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()) { Strings= i.next(); // must be called before you can call i.remove() // Do something i.remove(); }
Итераторы, возвращаемые методами iterator и ListIterator этого класса, быстродействующие: если список структурно изменен в любое время после создания итератора любым способом, кроме как с помощью собственных методов remove или add итератора, итератор выдаст ConcurrentModificationException . Таким образом, при одновременном изменении итератор завершается сбоем быстро и чисто, вместо того, чтобы рисковать произвольным, недетерминированным поведением в неопределенное время в будущем.
Возможно, многим новичкам непонятен тот факт, что выполнение итерации по списку с использованием конструкций for / foreach неявно создает итератор, который обязательно недоступен. Эту информацию можно найти здесь
Ответ 2
Вы не хотите этого делать. Это может вызвать неопределенное поведение в зависимости от коллекции. Вы хотите использовать итератор напрямую. Хотя конструкция for each является синтаксическим сахаром и на самом деле использует итератор, она скрывает его из вашего кода, поэтому вы не можете получить к нему доступ для вызова Iterator.remove.
Поведение итератора не определено, если базовая коллекция изменяется во время выполнения итерации каким-либо иным способом, кроме вызова этого метода.
Вместо этого напишите свой код:
List<String> names = .... Iterator<String> it = names.iterator(); while (it.hasNext()) {
Stringname= it.next(); // Do something it.remove(); }
Обратите внимание, что код вызывает Iterator.remove, а не List.remove.
Дополнение:
Даже если вы удаляете элемент, который еще не был повторен, вы все равно не хотите изменять коллекцию и затем использовать Iterator. Это может неожиданно изменить коллекцию и повлиять на будущие операции с Iterator.
Ответ 3
for (String name : newArrayList<String>(names)) { // Do something names.remove(nameToRemove); }
Вы клонируете список names и выполняете итерацию по клонированию, одновременно удаляя из исходного списка. Немного чище, чем верхний ответ.
Ответ 4
Дизайн java "расширенного цикла for" заключался в том, чтобы не подвергать итератор воздействию кода, но единственный способ безопасно удалить элемент - это получить доступ к итератору. Так что в этом случае вам придется делать это по-старому.:
Если в реальном коде расширенный цикл for действительно того стоит, то вы могли бы добавить элементы во временную коллекцию и вызвать removeAll в списке после цикла.
РЕДАКТИРОВАТЬ (повторное добавление): Нет, изменение списка каким-либо образом вне итератора.метод remove() во время итерации вызовет проблемы. Единственный способ обойти это - использовать CopyOnWriteArrayList , но это действительно предназначено для решения проблем параллелизма.
Самый дешевый (с точки зрения строк кода) способ удаления дубликатов - это выгрузить список в LinkedHashSet (а затем обратно в список, если вам нужно). Это сохраняет порядок вставки при удалении дубликатов.