Получить доступ

Итераторы: fail-fast и ConcurrentModificationException

Iterator и ListIterator, механизм modCount, причины CME даже в одном потоке и безопасное удаление.

Iterator и ListIterator

Iterator — единый способ обхода любой коллекции: hasNext, next и опциональный remove. Цикл for-each — это синтаксический сахар над Iterator, поэтому всё сказанное о нём относится и к for-each.

ListIterator (только для List) умеет больше: двунаправленный обход (hasPrevious/previous), вставку add и замену set по ходу обхода, а также индексы nextIndex/previousIndex.

Механизм fail-fast

Базовые коллекции ведут счётчик структурных изменений modCount. Итератор при создании запоминает его в expectedModCount и сверяет на каждом шаге; при расхождении бросает ConcurrentModificationException.

Это сделано намеренно как раннее обнаружение багов: структурно менять коллекцию во время её обхода нельзя. Гарантии тут best-effort — на CME нельзя закладываться в логике, это сигнал об ошибке, а не контракт.

CME случается и в одном потоке

Распространённое заблуждение, что CME — про многопоточность. Чаще всего его ловят в одном потоке: удаление элемента через сам список во время for-each меняет modCount, и следующий шаг итератора падает.

Также CME может возникнуть при модификации коллекции из другого потока без синхронизации — но это лишь один из случаев, а не суть механизма.

for (String s : list) {
    if (s.isBlank()) list.remove(s); // CME: меняем list, а не через итератор
}

Как удалять безопасно

Во время обхода удаляют через сам итератор: it.remove() согласованно обновляет expectedModCount. Ещё лаконичнее — removeIf(predicate), который делает один проход и корректно управляет modCount.

ListIterator позволяет безопасно и вставлять/заменять элементы по ходу обхода.

list.removeIf(String::isBlank); // безопасно и в один проход
// или:
for (Iterator<String> it = list.iterator(); it.hasNext();) {
    if (it.next().isBlank()) it.remove();
}

Weakly-consistent итераторы

Конкурентные коллекции (ConcurrentHashMap, CopyOnWriteArrayList) НЕ бросают CME: их итераторы weakly-consistent — они обходят данные, существовавшие на момент создания итератора, и могут (или нет) отразить более поздние изменения, не падая.

Это другой контракт: вместо fail-fast — устойчивость к конкурентным изменениям ценой того, что снимок может быть слегка устаревшим.

Как отвечать на собеседовании

Объясните fail-fast через modCount/expectedModCount, подчеркните, что CME — это про обнаружение структурной модификации во время обхода (часто в одном потоке), покажите безопасное удаление через iterator.remove/removeIf и противопоставьте weakly-consistent итераторы конкурентных коллекций.

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем