Коллекция бросает или не бросает ConcurrentModificationException на основе содержимого коллекции

Следующий код Java генерирует ConcurrentModificationException , как и ожидалось:

 public class Evil { public static void main(String[] args) { Collection c = new ArrayList(); c.add("lalala"); c.add("sososo"); c.add("ahaaha"); removeLalala(c); System.err.println(c); } private static void removeLalala(Collection c) { for (Iterator i = c.iterator(); i.hasNext();) { String s = i.next(); if(s.equals("lalala")) { c.remove(s); } } } } 

Но следующий пример, который отличается только содержимым Collection , выполняется без каких-либо исключений:

 public class Evil { public static void main(String[] args) { Collection c = new ArrayList(); c.add("lalala"); c.add("lalala"); removeLalala(c); System.err.println(c); } private static void removeLalala(Collection c) { for (Iterator i = c.iterator(); i.hasNext();) { String s = i.next(); if(s.equals("lalala")) { c.remove(s); } } } } 

Это печатает вывод «[lalala]». Почему второй пример не выдает ConcurrentModificationException при первом примере?

    Короткий ответ

    Потому что отказоустойчивое поведение iteratorа не гарантируется.

    Длительный ответ

    Вы получаете это исключение, потому что вы не можете манипулировать коллекцией, итерации по ней, за исключением iteratorа.

    Плохо:

     // we're using iterator for (Iterator i = c.iterator(); i.hasNext();) { // here, the collection will check it hasn't been modified (in effort to fail fast) String s = i.next(); if(s.equals("lalala")) { // s is removed from the collection and the collection will take note it was modified c.remove(s); } } 

    Хорошо:

     // we're using iterator for (Iterator i = c.iterator(); i.hasNext();) { // here, the collection will check it hasn't been modified (in effort to fail fast) String s = i.next(); if(s.equals("lalala")) { // s is removed from the collection through iterator, so the iterator knows the collection changed and can resume the iteration i.remove(); } } 

    Теперь к «почему»: в приведенном выше коде обратите внимание на то, как выполняется проверка модификации – удаление маркирует коллекцию как измененную, а следующая итерация проверяет любые изменения и терпит неудачу, если обнаруживает, что коллекция изменена. Еще одна важная вещь заключается в том, что ArrayList (не уверен в других коллекциях) не проверяет на модификацию hasNext() .

    Поэтому могут произойти две странные вещи:

    • Если вы удалите последний элемент во время итерации, ничего не будет выброшено
      • Это потому, что нет «следующего» элемента, поэтому итерация заканчивается перед достижением кода проверки модификации
    • Если вы удалите второй-последний элемент, ArrayList.hasNext() также вернет false , потому что current index iteratorа теперь указывает на последний элемент (бывший второй по-последнему).
      • Таким образом, даже в этом случае после удаления отсутствует следующий элемент

    Обратите внимание, что все это соответствует документации ArrayList :

    Обратите внимание, что отказоустойчивое поведение iteratorа не может быть гарантировано, поскольку, вообще говоря, невозможно сделать какие-либо серьезные гарантии при наличии несинхронизированной параллельной модификации. Неуправляемые iteratorы бросают ConcurrentModificationException с максимальной эффективностью. Поэтому было бы неправильно писать программу, зависящую от этого исключения за ее правильность: отказоустойчивое поведение iteratorов должно использоваться только для обнаружения ошибок.

    Отредактировано для добавления:

    Этот вопрос дает некоторую информацию о том, почему проверка сопутствующей модификации не выполняется в hasNext() и выполняется только в next() .

    Если вы посмотрите на исходный код для iteratorа ArrayList (частный вложенный class Itr ), вы увидите недостаток в коде.

    Код должен быть неудачным, что делается внутри iteratorа, вызывая checkForComodification() , однако hasNext() не делает этот вызов, вероятно, по соображениям производительности.

    hasNext() вместо этого просто:

     public boolean hasNext() { return cursor != size; } 

    Это означает, что когда вы находитесь на втором последнем элементе списка, а затем удаляете элемент (любой элемент), размер уменьшается и hasNext() думает, что вы на последнем элементе (которого вы не были) и возвращает false , пропуская итерацию последнего элемента без ошибок.

    ОЙ !!!!

    Из других ответов вы знаете, каков правильный способ удаления элемента в коллекции, когда вы выполняете итерацию коллекции. Я даю здесь объяснение основному вопросу. И ответ на ваш вопрос лежит в следующей трассировке стека

     Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(Unknown Source) at java.util.ArrayList$Itr.next(Unknown Source) at com.ii4sm.controller.Evil.removeLalala(Evil.java:23) at com.ii4sm.controller.Evil.main(Evil.java:17) 

    В stacktrace очевидно, что i.next(); строка выдает ошибку. Но когда у вас есть только два элемента в коллекции.

     Collection c = new ArrayList(); c.add("lalala"); c.add("lalala"); removeLalala(c); System.err.println(c); 

    Когда первый удаляется, i.hasNext() возвращает false, а i.next() никогда не выполняется для выброса исключения

    вы должны удалить из iterator (i) не collection (c) напрямую;

    попробуй это:

     for (Iterator i = c.iterator(); i.hasNext();) { String s = i.next(); if(s.equals("lalala")) { i.remove(); //note here, changing c to i with no parameter. } } 

    РЕДАКТИРОВАТЬ:

    Причина, по которой первая попытка вызывает исключение, а вторая – не просто из-за количества элементов в вашей коллекции.

    поскольку первый из них будет проходить через цикл более одного раза, а второй – только один раз. поэтому у него нет возможности исключить исключение

    Вы не можете удалить из списка, если вы просматриваете его с циклом «для каждого».

    Вы не можете удалить элемент из коллекции, которую вы выполняете. Вы можете обойти это, явно используя Итератор и удалив там элемент. Вы можете использовать Итератор.

    Если вы используете нижеприведенный код, вы не получите никакого исключения:

     private static void removeLalala(Collection c) { /*for (Iterator i = c.iterator(); i.hasNext();) { String s = i.next(); if(s.equals("lalala")) { c.remove(s); } }*/ Iterator it = c.iterator(); while (it.hasNext()) { String st = it.next(); if (st.equals("lalala")) { it.remove(); } } } 
    Давайте будем гением компьютера.