For-Each и указатели в Java

Итак, я пытаюсь перебрать ArrayList и удалить конкретный элемент. Однако у меня возникли проблемы с использованием структуры For-Each. Когда я запускаю следующий код:

ArrayList<String> arr = new ArrayList<String>();
//... fill with some values (doesn't really matter)

for(String t : arr)
{
  t = " some other value "; //hoping this would change the actual array
}

for(String t : arr)
{
  System.out.println(t); //however, I still get the same array here
}

Мой вопрос в том, как я могу сделать «t» указателем на «arr», чтобы я мог изменять значения в цикле for-each? Я знаю, что мог бы пройти через ArrayList, используя другую структуру, но этот выглядит настолько чистым и читаемым, что было бы неплохо иметь возможность сделать «t» указателем.

Все комментарии приветствуются! Даже если вы скажете, что я должен просто смириться и использовать другую конструкцию.


person Community    schedule 30.11.2009    source источник
comment
это не о циклах foreach, это более основное непонимание того, как работает java   -  person skaffman    schedule 01.12.2009
comment
Несмотря на то, что ArrayList поддерживается массивом: ArrayList != Array. Не путайте себя, смешивая термины (как в ваших комментариях)   -  person Andreas Dolk    schedule 01.12.2009


Ответы (11)


Я думаю, что лучшим подходом может быть использование цикла for.

    ArrayList<String> arr = new ArrayList<String>();

    for (int i = 0; i < arr.size(); i++) {

        String t = arr.get(i);

        if (// your condition is met) {
            arr.set(i, "your new value");
        }
    }
person Michael Bobick    schedule 30.11.2009
comment
Это работает? Нет ConcurrentModificationException таким образом? - person Andreas Dolk; 01.12.2009
comment
Я думаю, вы получите это исключение, только если вы добавляете/удаляете элементы из массива во время итерации - person Dónal; 01.12.2009
comment
Да, конечно, вы больше не используете итератор, просто обычный цикл for. Не обратил внимания ;) - person Andreas Dolk; 01.12.2009
comment
Я думаю, что лучше поместить arr.size() в локальную переменную, а не вычислять ее каждый раз снова и снова. - person Alfred; 01.12.2009
comment
Компилятор JIT, скорее всего, выведет вызов size() из цикла. - person Steve Kuo; 01.12.2009
comment
size() просто вернет .length резервного массива... и, как указывает Стив, это, скорее всего, будет встроено во время выполнения. В случае чего-то вроде Android, где нет JIT, создание временной переменной будет иметь улучшение. - person TofuBeer; 01.12.2009
comment
size() не возвращает длину резервного массива. Возможно, вы думаете о Arrays.asList(). - person Kevin Bourrillion; 01.12.2009

Проблема в том, что вы пытаетесь изменить ссылку t в области цикла, чтобы она указывала на новый экземпляр String. Это не сработает. Он не ссылается на фактическую запись в arraylist. Вам нужно изменить фактическое значение ссылки. Если бы String был изменчивым и предоставлял для этого фиктивный метод set(), теоретически вы могли бы сделать

for (String t : arr) {
    t.set("some other value");
}

или около того, но это невозможно, поскольку это неизменно. Лучше получить дескриптор точки входа в самом массиве, используя обычный цикл for:

for (int i = 0; i < arr.size(); i++) {
    arr.set(i, "some other value");
}

Если вы настаиваете на использовании расширенного цикла for, вам нужно заменить String на StringBuilder, который является изменяемым:

for (StringBuilder t : arr) {
     t.delete(0, t.length()).append("some other value");
}

Помните, что в Java передача осуществляется по значению, а не по ссылке.

person BalusC    schedule 30.11.2009
comment
Еще хуже - его первое предложение говорит нам, что он хочет что-то удалить (имеет смысл, похоже, это фактическое назначение для example.buab-user-group ;)) но его код показывает замену... это будет ооооочень долгая ночь :) - person Andreas Dolk; 01.12.2009
comment
Wtf, он хотел удалить его. Там вам нужен святой Iterator для. - person BalusC; 01.12.2009

For-each не дает вам указателя индекса, поэтому вы просто не можете использовать его для изменения неизменяемого значения.

Либо используйте цикл for с индексом, либо используйте изменяемый тип (например, StringBuffer, а не String)

person Bill K    schedule 30.11.2009

Массив объектов (например, строк) в Java — это непрерывный блок, содержащий упорядоченный ряд ссылок. Итак, когда у вас есть массив из 4 строк, на самом деле у вас есть 4 ссылки, хранящиеся В массиве, и 4 строковых объекта, которые находятся за пределами массива, но на которые ссылаются его 4 элемента.

Что делает конструкция for-each в Java, так это создает локальную переменную и для каждой итерации копирует в эту локальную переменную ссылку из ячейки массива, которая соответствует этой итерации. Когда вы устанавливаете переменную цикла (t = " some other value"), вы помещаете ссылку на новую строку "some other value" в локальную переменную t, а не в массив.

В отличие от некоторых других языков (например, Perl), где переменная цикла действует как псевдоним для самого элемента массива/списка.

person Nate C-K    schedule 30.11.2009
comment
+1 за прекрасное объяснение! - person Hassan; 11.09.2011

Ваш код переписывается компилятором примерно так:

ArrayList<String> arr = new ArrayList<String>();
//... fill with some values (doesn't really matter)

for (final Iterator <String> i = arr.iterator(); i.hasNext();) {
    String t;

    t = i.next();
    t = " some other value "; // just changes where t is pointing
}

Чтобы сделать то, что вы хотите, вам нужно будет написать цикл for следующим образом:

for (final ListIterator<String> i = arr.iterator(); i.hasNext();) {
     final String t;

     t = i.next();
     i.set("some other value");
}

У Iterator нет метода set, он есть только у ListIterator.

person TofuBeer    schedule 01.12.2009

В основном вы хотите удалить строку t из списка обр. Просто сделайте arr.remove(t) и все готово. Но вы не можете сделать это, перебирая один и тот же список. Вы получите исключение, если попытаетесь изменить список таким образом.

У вас есть два варианта:

  1. клонируйте свой список, выполните итерацию по клону и удалите «конкретную» строку из исходного списка
  2. создайте список для кандидатов на удаление, добавьте в этот список все «конкретные» строки и, после повторения исходного списка, выполните итерацию по мусорной корзине и удалите все, что вы собрали здесь, из исходного списка.

Вариант 1 самый простой, клон можно сделать так:

List<String> clone = new ArrayList<String>(arr);
person Andreas Dolk    schedule 30.11.2009

Похоже, вы неправильно понимаете, как объекты/ссылки работают в Java, что очень важно для эффективного использования языка. Однако этот код здесь должен делать то, что вы хотите (извините за отсутствие объяснений):

ArrayList<String> arr = new ArrayList<String>();
//... fill with some values (doesn't really matter)

for(int i = 0; i < arr.size(); i++)
{
  arr.set(i, " some other value "); // change the contents of the array
}

for(String t : arr)
{
  System.out.println(t); 
}
person Dónal    schedule 30.11.2009

Я считаю, что это не связано с неизменным или изменчивым.

 t = " some other value "; //hoping this would change the actual array

t не содержит ссылку на фактический объект. Java копирует значение из arraylist и помещает это значение в t, поэтому значение списка массива не влияет.

ХТН

person nayakam    schedule 01.12.2009
comment
это на самом деле больше похоже на то, что я думал изначально. Мне было интересно, могу ли я получить ссылку на объект, но, поскольку он неизменяем, я думаю, это все равно не имеет значения. - person ; 01.12.2009

На это хорошо ответили. Тем не менее, вот мое предложение. Внутренний цикл var t виден только там. Это не будет видно вне цикла. Вы могли бы сделать t.set(), если бы не String.

person fastcodejava    schedule 01.12.2009

Используйте StringBuffer вместо простых строк. Таким образом, строка внутри является изменяемой.

person PL.    schedule 28.04.2010

Строки неизменяемы. Если бы у вас был изменяемый тип, такой как StringBuilder/Buffer, вы могли бы изменить строку в своей итерации. У вас есть ссылки, помните.

person Stefan Kendall    schedule 30.11.2009
comment
Это не имеет ничего общего с String или StringBuilder или неизменностью, это то, как ссылки на объекты работают в Java. - person skaffman; 01.12.2009
comment
Код присваивается локальной переменной t, это не повлияет на ссылку внутри ArrayList. Чтобы изменить это, вам нужно вызвать arr.set() в соответствии с ответом MB ниже. - person SimonJ; 01.12.2009
comment
...какая? Итерация через ArrayList‹StringBuilder›, безусловно, позволит вам изменить объект внутри строки в соответствии с определенной спецификацией OP. Он сказал, что ему нужен стиль, а не точная функциональность указателя. Вы все марионетки. - person Stefan Kendall; 01.12.2009
comment
Люди, точка неизменности связана с комментарием OP // в надежде, что это изменит фактический массив. Так что этот ответ не хуже, чем ответ MB с большим количеством голосов, который заменяет объект в позиции элемента массива новым объектом. - person Murali VP; 01.12.2009
comment
@Stefan Вы не можете изменить ссылку, о которой задавался вопрос. StringBuilder = будет таким же, как String =. Ваше предложение на самом деле может быть опасным, поскольку оно может изменить переменную вне списка. - person TofuBeer; 01.12.2009
comment
@Tofu: Ты вообще читал вопрос ОП? Он спросил об изменении ЗНАЧЕНИЯ, а не ссылки на указатель. Существует множество ситуаций, когда переменная помещается в список, который затем массово модифицируется. Это не потенциально опасно. Это функции, как задумано. - person Stefan Kendall; 01.12.2009
comment
@TofuBeer Стефан прав, вы бы не назначали, а использовали метод замены StringBuilder. Итак, вы говорите, что нельзя вообще изменить элемент arraylist? И что вы имеете в виду изменить вне списка? В любом случае вы не можете изменить изменяемый объект внутри списка (что бы это ни значило). Это правильный ответ на вопрос ОП, но я думаю, что не многие люди думают так, как я. - person Murali VP; 01.12.2009
comment
Я не сказал, что вы не можете изменить запись, я сказал, что вы не можете заменить ее заданием. Это решение работает только для изменяемых элементов, и очевидно, что приведенный пример был заменой, а не модификацией. - person TofuBeer; 01.12.2009