Вопрос-ответ

Calling remove in foreach loop in Java [duplicate]

Вызов remove в цикле foreach в Java [дубликат]

В Java законно ли вызывать remove для коллекции при выполнении итерации по коллекции с использованием цикла foreach? Например:

List<String> names = ....
for (String name : names) {
// Do something
names.remove(name).
}

В качестве дополнения, законно ли удалять элементы, которые еще не были повторены? Например,

//Assume that the names list as duplicate entries
List<String> names = ....
for (String name : names) {
// Do something
while (names.remove(name));
}
Переведено автоматически
Ответ 1

Для безопасного удаления из коллекции во время итерации по ней вам следует использовать итератор.

Например:

List<String> names = ....
Iterator<String> i = names.iterator();
while (i.hasNext()) {
String s = i.next(); // must be called before you can call i.remove()
// Do something
i.remove();
}

Из документации Java :


Итераторы, возвращаемые методами iterator и ListIterator этого класса, быстродействующие: если список структурно изменен в любое время после создания итератора любым способом, кроме как с помощью собственных методов remove или add итератора, итератор выдаст ConcurrentModificationException . Таким образом, при одновременном изменении итератор завершается сбоем быстро и чисто, вместо того, чтобы рисковать произвольным, недетерминированным поведением в неопределенное время в будущем.


Возможно, многим новичкам непонятен тот факт, что выполнение итерации по списку с использованием конструкций for / foreach неявно создает итератор, который обязательно недоступен. Эту информацию можно найти здесь

Ответ 2

Вы не хотите этого делать. Это может вызвать неопределенное поведение в зависимости от коллекции. Вы хотите использовать итератор напрямую. Хотя конструкция for each является синтаксическим сахаром и на самом деле использует итератор, она скрывает его из вашего кода, поэтому вы не можете получить к нему доступ для вызова Iterator.remove.


Поведение итератора не определено, если базовая коллекция изменяется во время выполнения итерации каким-либо иным способом, кроме вызова этого метода.


Вместо этого напишите свой код:

List<String> names = ....
Iterator<String> it = names.iterator();
while (it.hasNext()) {

String name = it.next();
// Do something
it.remove();
}

Обратите внимание, что код вызывает Iterator.remove, а не List.remove.

Дополнение:

Даже если вы удаляете элемент, который еще не был повторен, вы все равно не хотите изменять коллекцию и затем использовать Iterator. Это может неожиданно изменить коллекцию и повлиять на будущие операции с Iterator.

Ответ 3
for (String name : new ArrayList<String>(names)) {
// Do something
names.remove(nameToRemove);
}

Вы клонируете список names и выполняете итерацию по клонированию, одновременно удаляя из исходного списка. Немного чище, чем верхний ответ.

Ответ 4

Дизайн java "расширенного цикла for" заключался в том, чтобы не подвергать итератор воздействию кода, но единственный способ безопасно удалить элемент - это получить доступ к итератору. Так что в этом случае вам придется делать это по-старому.:

 for(Iterator<String> i = names.iterator(); i.hasNext();) {
String name = i.next();
//Do Something
i.remove();
}

Если в реальном коде расширенный цикл for действительно того стоит, то вы могли бы добавить элементы во временную коллекцию и вызвать removeAll в списке после цикла.

РЕДАКТИРОВАТЬ (повторное добавление): Нет, изменение списка каким-либо образом вне итератора.метод remove() во время итерации вызовет проблемы. Единственный способ обойти это - использовать CopyOnWriteArrayList , но это действительно предназначено для решения проблем параллелизма.

Самый дешевый (с точки зрения строк кода) способ удаления дубликатов - это выгрузить список в LinkedHashSet (а затем обратно в список, если вам нужно). Это сохраняет порядок вставки при удалении дубликатов.

java