Как избежать "ConcurrentModificationException" при удалении элементов из "ArrayList" при его повторении?

Я пытаюсь удалить некоторые элементы из ArrayList, итерации следующим образом:

for (String str : myArrayList) {
    if (someCondition) {
        myArrayList.remove(str);
    }
}

Конечно, я получаю ConcurrentModificationException при попытке удалить элементы из списка одновременно при повторении myArrayList. Есть ли какое-то простое решение для решения этой проблемы?

Ответ 1

Используйте Iterator и вызывайте remove():

Iterator<String> iter = myArrayList.iterator();

while (iter.hasNext()) {
    String str = iter.next();

    if (someCondition)
        iter.remove();
}

Ответ 2

В качестве альтернативы всем остальным ответам я всегда делал что-то вроде этого:

List<String> toRemove = new ArrayList<>();
for (String str : myArrayList) {
    if (someCondition) {
        toRemove.add(str);
    }
}
myArrayList.removeAll(toRemove);

Это позволит вам напрямую обращаться к итератору, но требует другого списка. Я всегда предпочитал этот маршрут по любой причине.

Ответ 3

Пользователь Java 8 может сделать это: list.removeIf(...)

    List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
    list.removeIf(e -> (someCondition));

Он удалит элементы в списке, для которых выполняется некоторое условие

Ответ 4

Вы должны использовать метод iterator remove(), что означает, что для цикла не было усилено:

for (final Iterator iterator = myArrayList.iterator(); iterator.hasNext(); ) {
    iterator.next();
    if (someCondition) {
        iterator.remove();
    }
}

Ответ 5

Нет, нет, НЕТ!

В одиночных задачах вам не нужно использовать Iterator, более того, CopyOnWriteArrayList (из-за повышения производительности).

Решение намного проще: попытайтесь использовать канонический цикл для цикла, а не для каждого цикла.

В соответствии с владельцами авторских прав Java (несколько лет назад Sun, теперь Oracle) для каждого руководства по циклам, он использует итератор для просмотра коллекции и просто скрывает его, чтобы сделать код выглядит лучше. Но, к сожалению, как мы видим, это вызвало больше проблем, чем прибыли, иначе эта тема не возникла бы.

Например, этот код приведет к java.util.ConcurrentModificationException при вводе следующей итерации в модифицированном ArrayList:

        // process collection
        for (SomeClass currElement: testList) {

            SomeClass founDuplicate = findDuplicates(currElement);
            if (founDuplicate != null) {
                uniqueTestList.add(founDuplicate);
                testList.remove(testList.indexOf(currElement));
            }
        }

Но следующий код работает просто отлично:

    // process collection
    for (int i = 0; i < testList.size(); i++) {
        SomeClass currElement = testList.get(i);

        SomeClass founDuplicate = findDuplicates(currElement);
        if (founDuplicate != null) {
            uniqueTestList.add(founDuplicate);
            testList.remove(testList.indexOf(currElement));
            i--; //to avoid skipping of shifted element
        }
    }

Итак, попробуйте использовать метод индексирования для итерации по коллекциям и избегайте цикла for-each, поскольку они не эквивалентны! Для каждого цикла используются некоторые внутренние итераторы, которые проверяют модификацию коллекции и исключают исключение ConcurrentModificationException. Чтобы это подтвердить, более подробно рассмотрите трассировку печатного стека при первом примере, который я опубликовал:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
    at java.util.AbstractList$Itr.next(AbstractList.java:343)
    at TestFail.main(TestFail.java:43)

Для многопоточности используются соответствующие многозадачные подходы (например, синхронизированное ключевое слово).

Ответ 6

В то время как другие предлагаемые решения работают, если вы действительно хотите, чтобы решение было выполнено в потоковом режиме, вы должны заменить ArrayList CopyOnWriteArrayList

    //List<String> s = new ArrayList<>(); //Will throw exception
    List<String> s = new CopyOnWriteArrayList<>();
    s.add("B");
    Iterator<String> it = s.iterator();
    s.add("A");

    //Below removes only "B" from List
    while (it.hasNext()) {
        s.remove(it.next());
    }
    System.out.println(s);

Ответ 7

Если вы хотите изменить свой список во время обхода, вам нужно использовать Iterator. И затем вы можете использовать iterator.remove() для удаления элементов во время обхода.

Ответ 8

List myArrayList  = Collections.synchronizedList(new ArrayList());

//add your elements  
 myArrayList.add();
 myArrayList.add();
 myArrayList.add();

synchronized(myArrayList) {
    Iterator i = myArrayList.iterator(); 
     while (i.hasNext()){
         Object  object = i.next();
     }
 }

Ответ 9

Один альтернативный метод преобразует ваши List в array, перебирает их и удаляет их непосредственно из List на основе вашей логики.

List<String> myList = new ArrayList<String>(); // You can use either list or set

myList.add("abc");
myList.add("abcd");
myList.add("abcde");
myList.add("abcdef");
myList.add("abcdefg");

Object[] obj = myList.toArray();

for(Object o:obj)  {
    if(condition)
        myList.remove(o.toString());
}

Ответ 10

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

из здесь