Страницы

Поиск по вопросам

понедельник, 25 ноября 2019 г.

Удаление элемента списка в цикле foreach не бросает ConcurrentModificationException, почему?


Допустим, у меня есть некий ArrayList.

ArrayList list = new ArrayList<>();

for (int i = 0; i < 10; i++)
  list.add((int) (Math.random() * 20));


И я хочу из него удалить все числа больше 10.

"Правильно" сделать это можно через итератор, получив гарантированный результат.

for (Iterator iterator = list.iterator(); iterator.hasNext(); )
    if (iterator.next() > 10)
        iterator.remove();


Но на мое удивление, корректно работает и вариант:

for (Integer i : list)
    if (i > 10)
        list.remove(i);


Несмотря на то, что везде говорится, что любая попытка удаления из коллекции в цикле без использования итератора приводит к ConcurrentModificationException.

И, действительно, если будем удалять безусловно, получим как раз ConcurrentModificationException

for (Integer i : list)
      list.remove(i);


Собственно, может кто-нибудь объяснить магию или дать наводку, где можно почитать на эту тему?
    


Ответы

Ответ 1



Видимо вам сказочно повезло: у вас не было элементов, значение которых было больше 10 у вас был элемент, значение которого было больше 10, но он был только один и располагался на предпоследнем месте в списке Давайте рассмотрим работу на более коротком примере. ArrayList list = new ArrayList<>(); list.add(1); list.add(22); list.add(3); Мы добавили три числа. Итак, как работает foreach Он получает итератор. Проверяет на наличие следующего элемента hasNext(). public boolean hasNext() { return cursor != size(); // cursor is zero initially. } Если возвращается true, то берет следующий элемент с помощьюnext(). public E next() { checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } final void checkForComodification() { // Initially modCount = expectedModCount (our case 5) if (modCount != expectedModCount) throw new ConcurrentModificationException(); } Далее повторяются шаги 2 и 3 пока hasNext() не вернет false. Если удалить элемент из списка, то его размер уменьшится и modCount увеличится. Если удалить элемент во время итерации, то будет выброшено ConcurrentModificationException исключение на строке modCount != expectedModCount. Но что происходит, если удаляется предпоследний элемент? > cursor = 0 size = 3 --> hasNext() успешно и next() тоже без эксепшена > cursor = 1 size = 3 --> hasNext() успешно и next() тоже без эксепшена Когда мы удалим значение 22, то размер уменьшится до 2. > cursor = 2 size = 2 --> hasNext() не успешно и next() пропускается. В других же случаях будет выброшено ConcurrentModificationException из-за modCount != expectedModCount. А в этом единичном случае проверка пройдет на ура.. Вот магия....Или баг....

Ответ 2



Да нечего читать на эту тему. В документации ясно сказано, что для удаления элeментов из коллекции нужно использовать итератор. Тот факт, что в вашем примере вы смогли удалить числа больше 10 обусловлен скорее всего тeм, что там не было таких чисел или было только одно - предпоследнее. Хотя реалиция foreach основана на использовании итератора, но если вы представит что внутри foreach обыкновенный for (int i = 0; i < 10; i++) то я думаю будет очевидно почему нельзя удалять элементы из массива пока вы его обходите в цикле. А вообще, используйте Java 8, и забудьте о проблемах :) List list = new ArrayList(); list.removeIf(e -> e > 10);

Комментариев нет:

Отправить комментарий