Вызов 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();
}

С Java Документация :

итераторы, возвращаемые итератором и listIterator этого класса методы быстры: если список структурно изменен в любом случае время после создания итератора, кроме как через собственные методы удаления или добавления итератора, итератор будет бросать ConcurrentModificationException. Таким образом, при одновременном модификации, iterator перестал работать быстро и чисто, а не риск произвольного, недетерминированного поведения в неопределенное время в будущем.

возможно, многим новичкам неясен тот факт, что итерация по списку с использованием конструкций 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 в списке после цикла.

EDIT (re addendum): Нет, изменение списка каким-либо образом вне итератора.удалять() метод при итерации вызовет проблемы. Единственный способ обойти это-использовать 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 большой список или дорогой "добавить", в сочетании с только несколько удалены по сравнению с размером списка. Было бы более эффективно сделать только несколько удалений, а не очень много добавлений. Но в моем случае ситуация не заслуживала такой оптимизации.


  1. попробуйте это 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;

Итак, если вы удалите элемент из списка, список будет реструктурирован, индекс другого элемента будет изменен, это может привести к тому, что вы хотите, чтобы произошло.


использовать

.удалить () из Interator или

использовать

CopyOnWriteArrayList