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 итераторы конкурентных коллекций.