Сначала ответь вслух
Скажи ответ вслух хотя бы пару минут, потом открой эталон и сравни. Эталон открыт всем и не требует входа.
Показать ответ
Эталонный ответ
Цикл for-each по списку работает через итератор. Итератор хранит expectedModCount, а список — modCount, который растёт при каждом структурном изменении. next() сравнивает их и при расхождении бросает ConcurrentModificationException.
List<String> names = new ArrayList<>(List.of("Анна", "Борис", "Вера", "Глеб"));
for (String name : names) {
if (name.startsWith("Б")) names.remove(name); // следующий next() бросит CME
}
names.removeIf(name -> name.startsWith("Б")); // правильноПравильно удалять через removeIf или через Iterator.remove(): итератор сам поправит свой счётчик.
Как у тебя получилось?
Уточняющие вопросы
Так обычно копают глубже. Раскрой строку, когда ответишь вслух.
Уточнение 1-го уровня: Почему удаление предпоследнего элемента не бросает исключение?
Ответ уровня Middle
После удаления размер списка становится равен позиции итератора, hasNext() возвращает false, и цикл заканчивается раньше. Исключения нет, но последний элемент не проверен.
Уточнение 2-го уровня: Можно ли полагаться на fail-fast в многопоточном коде?
Ответ уровня Middle
Нет: проверка работает по принципу «по возможности» и ничего не гарантирует. Для обхода во время изменений нужны потокобезопасные коллекции.
Типичные ошибки
- Ловить ConcurrentModificationException вместо того, чтобы удалять правильно.
- Удалять по индексу в цикле вперёд и пропускать соседние элементы.
Первоисточники
Быстрые задания по теме
Для темы «Итераторы и fail-fast» быстрых заданий пока нет
Можно проверить всю зону «Коллекции» тестом на 10 минут.
Проверить себя за 10 минут