Как исправить ошибку при удалении элементов из коллекции во время итерации
Что проверяют
Понимаете ли вы, откуда берётся ConcurrentModificationException и что оно возникает в одном потоке, без всякой многопоточности. Следом обычно спрашивают, как устроены fail-fast итераторы и чем от них отличаются итераторы CopyOnWriteArrayList и ConcurrentHashMap.
Как отвечать
Начните с причины. У ArrayList, HashMap и других коллекций из java.util есть счётчик структурных изменений modCount. Итератор запоминает его при создании и сверяет при каждом next(). Цикл for (String s : list) работает через итератор, поэтому list.remove(s) внутри него меняет счётчик, и на следующем шаге летит исключение.
Способы исправить:
Iterator.remove(): удаляет текущий элемент и синхронизирует счётчик. Классический вариант для любой коллекции;removeIf: с Java 8 есть у любойCollection, для карт работает черезmap.entrySet().removeIf(...)илиmap.values().removeIf(...). Короче и уArrayListбыстрее: элементы сдвигаются один раз, а не на каждое удаление;- новая коллекция:
list.stream().filter(...).toList(), если исходную менять не обязательно. Учтите, чтоtoList()возвращает неизменяемый список; - цикл по индексу с конца:
for (int i = list.size() - 1; i >= 0; i--), удаление не сдвигает ещё не пройденные элементы; - конкурентные коллекции: итератор
CopyOnWriteArrayListидёт по снимку и не падает, но каждое изменение копирует массив.ConcurrentHashMapдаёт слабо согласованные итераторы. Это решение для многопоточности, а не заплатка для одного цикла.
Где спотыкаются
Удаляют в прямом цикле for (int i = 0; i < list.size(); i++). Исключения нет, но после удаления элементы сдвигаются, и соседний элемент пропускается.
Считают, что исключение гарантировано. Документация называет fail-fast поведение работой «по возможности», и удаление предпоследнего элемента ArrayList в foreach проходит молча: hasNext() возвращает false, и баг прячется до другого набора данных.
Оборачивают список в Collections.synchronizedList и считают проблему решённой. Обход такого списка всё равно требует внешней синхронизации, а однопоточный случай он не лечит вовсе.