Вопрос проверяет понимание внутреннего устройства итераторов в Java и различий между структурной модификацией коллекции и изменением значений элементов.
Цикл for-each неявно использует итератор коллекции. Итератор в Java (например, в ArrayList, HashMap) реализует fail-fast поведение: он хранит внутренний счетчик modCount, который увеличивается при каждом структурном изменении коллекции (добавление, удаление элементов). Когда итератор проверяет наличие следующего элемента, он сравнивает свой ожидаемый modCount с текущим modCount коллекции. Если они различаются, выбрасывается ConcurrentModificationException.
Пример кода, вызывающий исключение:
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String s : list) {
if (s.equals("B")) {
list.remove(s); // структурное изменение — исключение
}
}Здесь list.remove(s) изменяет структуру списка, увеличивая modCount, но итератор об этом не знает — при следующей проверке hasNext() или next() возникает исключение.
Метод keySet() возвращает представление (view) ключей Map. Итерация по keySet также использует итератор, но замена значения через map.put(key, newValue) не является структурным изменением Map — она лишь обновляет значение, связанное с существующим ключом. Структура (набор ключей) остается неизменной, поэтому modCount не увеличивается, и исключение не возникает.
Пример безопасной замены:
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
for (String key : map.keySet()) {
if (key.equals("A")) {
map.put(key, 10); // замена значения — безопасно
}
}Обратите внимание: если бы вы попытались удалить ключ через map.remove(key) внутри цикла, это было бы структурным изменением и вызвало бы исключение.
ConcurrentModificationException защищает от непреднамеренных структурных изменений коллекции во время итерации. Замена значений не затрагивает структуру, поэтому безопасна. Для удаления элементов используйте Iterator.remove() или Collectors, чтобы избежать исключения.