Вызов 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));
}
11 ответов:
чтобы безопасно удалить из коллекции во время итерации по нему, вы должны использовать итератор.
например:
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(); }итераторы возвращаются Iterator и listIterator этого класса методы быстродействующие: если список структурно изменен в любом случае время после создания итератора, кроме как через собственные методы удаления или добавления итератора, итератор бросит ConcurrentModificationException. Таким образом, при одновременном модификация, итератор выходит из строя быстро и чисто, а не риск произвольного, недетерминированного поведения в неопределенное время в будущем.
возможно, многим новичкам неясен тот факт, что итерация по списку с использованием конструкций for/foreach неявно создает итератор, который обязательно недоступен. Эту информацию можно найти здесь
вы не хотите этого делать. Это может вызвать неопределенное поведение в зависимости от коллекции. Вы хотите использовать итератор напрямую. Хотя для каждой конструкции является синтаксическим сахаром и действительно использует итератор, он скрывает его от вашего кода, поэтому вы не можете получить к нему доступ для вызова
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.
дизайн java "расширенного цикла for" состоял в том, чтобы не подвергать итератор коду, но единственный способ безопасно удалить элемент-это получить доступ к итератору. Так что в этом случае вы должны сделать это старая школа:
for(Iterator<String> i = names.iterator(); i.hasNext();) { String name = i.next(); //Do Something i.remove(); }Если в реальном коде цикл enhanced for действительно стоит того, то вы можете добавить элементы во временную коллекцию и вызвать removeAll в списке после цикла.
изменить (повторного добавления): нет, изменения список в любом случае за пределами итератора.удалять() метод во время итерации вызовет проблемы. Единственный способ обойти это-использовать CopyOnWriteArrayList, но это действительно предназначено для проблем параллелизма.
самый дешевый (с точки зрения строк кода) способ удалить дубликаты-это сбросить список в LinkedHashSet (а затем обратно в список, если вам нужно). Это сохраняет порядок вставки при удалении дубликатов.
for (String name : new ArrayList<String>(names)) { // Do something names.remove(nameToRemove); }вы клонируете список
namesи повторять через клон при удалении из исходного списка. Немного чище, чем верхний ответ.
Я не знаю о итераторов, однако вот что я делал до сегодняшнего дня, чтобы удалить элементы из списка внутри цикла:
List<String> names = .... for (i=names.size()-1;i>=0;i--) { // Do something names.remove(i); }это всегда работает и может использоваться в других языках или структурах, не поддерживающих итераторы.
Да вы можете использовать цикл for-each, Для этого вы должны поддерживать отдельный список для хранения удаления элементов, а затем удалить этот список из списка имен с помощью
removeAll()методList<String> names = .... // introduce a separate list to hold removing items List<String> toRemove= new ArrayList<String>(); for (String name : names) { // Do something: perform conditional checks toRemove.add(name); } names.removeAll(toRemove); // now names list holds expected values
те, кто говорит, что вы не можете безопасно удалить элемент из коллекции, кроме как через итератор, не совсем корректны, вы можете сделать это безопасно, используя одну из параллельных коллекций, таких как ConcurrentHashMap.
убедитесь, что это не запах кода. Можно ли изменить логику и быть "включающим", а не "исключающим"?
List<String> names = .... List<String> reducedNames = .... for (String name : names) { // Do something if (conditionToIncludeMet) reducedNames.add(name); } return reducedNames;ситуация, которая привела меня на эту страницу, включала старый код, который петлял по списку, используя indecies для удаления элементов из списка. Я хотела переделать его, чтобы использовать стиль для каждого.
он прошел через весь список элементов, чтобы проверить, какие из них пользователь имел разрешение на доступ, и удалил те, которые не имели разрешение из списка.
List<Service> services = ... for (int i=0; i<services.size(); i++) { if (!isServicePermitted(user, services.get(i))) services.remove(i); }чтобы отменить это и не использовать remove:
List<Service> services = ... List<Service> permittedServices = ... for (Service service:services) { if (isServicePermitted(user, service)) permittedServices.add(service); } return permittedServices;когда предпочтительнее будет "удалить"? Одно из соображений, если gien большой список или дорогой "добавить", в сочетании с только несколько удалены по сравнению с размером списка. Это может быть более эффективным, чтобы сделать только несколько удаляет, а не очень много добавляет. Но в моем случае ситуация не заслуживала такой оптимизации.
- попробуйте это 2. и измените состояние на "зимнее", и вы будете удивляться:
public static void main(String[] args) { Season.add("Frühling"); Season.add("Sommer"); Season.add("Herbst"); Season.add("WINTER"); for (String s : Season) { if(!s.equals("Sommer")) { System.out.println(s); continue; } Season.remove("Frühling"); } }
лучше использовать итератор, когда вы хотите удалить элемент из списка
потому что исходный код remove
if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null;Итак, если вы удалите элемент из списка ,список будет реструктурирован, индекс другого элемента будет изменен, это может привести к тому, что вы хотите, чтобы произошло.
Comments