← Назад к вопросам

ConcurrentModificationException при итерации ArrayList

1.2 Junior🔥 231 комментариев
#ООП#Основы Java

Условие

Что произойдёт при выполнении следующего кода?

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) {
    if (s.equals("b")) {
        list.add("d");
    }
}

Вопросы

  1. Что произойдёт при выполнении?
  2. Почему возникает исключение?
  3. Что такое fail-fast итератор?
  4. Как правильно модифицировать коллекцию во время итерации?

Подсказка

Рассмотрите использование Iterator.remove(), CopyOnWriteArrayList или создание копии списка.

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

ConcurrentModificationException при итерации ArrayList

Ответ

Программа выбросит: ConcurrentModificationException

Почему возникает исключение?

Это происходит из-за механизма fail-fast итератора, встроенного в ArrayList. Когда вы изменяете размер списка во время итерации, итератор обнаруживает это и выбрасывает исключение.

Как это работает под капотом

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

for (String s : list) {  // for-each использует итератор
    if (s.equals("b")) {
        list.add("d");   // ОШИБКА! Модифицируем список напрямую
    }
}
// java.util.ConcurrentModificationException

Внутреннее представление

// Примерно вот что происходит при for-each:

Iterator<String> iterator = list.iterator();
int modCount = list.modCount;  // Сохраняем начальное значение

while (iterator.hasNext()) {
    String s = iterator.next();  // Проверяет: modCount == list.modCount
    
    if (s.equals("b")) {
        list.add("d");           // modCount++ (теперь modCount != сохранённое)
    }
}
// При следующем вызове next() будет: throw ConcurrentModificationException

Что такое fail-fast итератор?

Fail-fast — это механизм, который сразу обнаруживает конкурентную модификацию коллекции и выбрасывает исключение, вместо того чтобы:

  • Молча пропустить элементы
  • Вернуть неправильные результаты
  • Привести к неопределённому поведению
// Внутренняя реализация ArrayList.iterator():

private class Itr implements Iterator<E> {
    int expectedModCount = modCount;
    
    public E next() {
        checkForComodification();
        // ...
    }
    
    final void checkForComodification() {
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
}

Решение 1: Использовать Iterator.remove()

Это безопасный способ удаления элементов:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String s = iterator.next();
    if (s.equals("b")) {
        iterator.remove();  // Безопасное удаление
    }
}

System.out.println(list);  // [a, c]

Для добавления элементов используйте ListIterator:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {
    String s = iterator.next();
    if (s.equals("b")) {
        iterator.add("d");  // Безопасное добавление
    }
}

System.out.println(list);  // [a, b, d, c]

Решение 2: Создать копию списка

Итерируйте по копии, модифицируйте оригинал:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

for (String s : new ArrayList<>(list)) {  // Итерируем по копии
    if (s.equals("b")) {
        list.add("d");                     // Модифицируем оригинал
    }
}

System.out.println(list);  // [a, b, c, d]

Решение 3: Использовать CopyOnWriteArrayList

Для многопоточного кода, когда много читателей:

import java.util.concurrent.CopyOnWriteArrayList;

List<String> list = new CopyOnWriteArrayList<>(Arrays.asList("a", "b", "c"));

for (String s : list) {
    if (s.equals("b")) {
        list.add("d");  // Безопасно! Не выбросит исключение
    }
}

System.out.println(list);  // [a, b, c, d]

Как работает CopyOnWriteArrayList:

  • При изменении создаёт копию массива
  • Итератор работает с "снимком" данных
  • Хороша для частого чтения, редкой записи
  • Медленнее для частых изменений

Решение 4: Использовать Stream API (функциональный подход)

Самый современный способ:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

List<String> result = list.stream()
    .flatMap(s -> s.equals("b") ? Stream.of(s, "d") : Stream.of(s))
    .collect(Collectors.toList());

System.out.println(result);  // [a, b, d, c]

Полный пример со всеми методами

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.*;

public class ConcurrentModificationDemo {
    
    // НЕПРАВИЛЬНО - выбросит исключение
    static void wrongWay() {
        List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
        try {
            for (String s : list) {
                if (s.equals("b")) {
                    list.add("d");
                }
            }
        } catch (ConcurrentModificationException e) {
            System.out.println("Ошибка: " + e.getMessage());
        }
    }
    
    // Способ 1: Iterator.remove()
    static void methodIteratorRemove() {
        List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String s = iterator.next();
            if (s.equals("a")) {
                iterator.remove();
            }
        }
        System.out.println("Iterator.remove(): " + list);
    }
    
    // Способ 2: Копия списка
    static void methodCopy() {
        List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
        for (String s : new ArrayList<>(list)) {
            if (s.equals("b")) {
                list.add("d");
            }
        }
        System.out.println("Копия списка: " + list);
    }
    
    // Способ 3: CopyOnWriteArrayList
    static void methodCopyOnWrite() {
        List<String> list = new CopyOnWriteArrayList<>(Arrays.asList("a", "b", "c"));
        for (String s : list) {
            if (s.equals("b")) {
                list.add("d");
            }
        }
        System.out.println("CopyOnWriteArrayList: " + list);
    }
    
    // Способ 4: Stream API
    static void methodStream() {
        List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
        List<String> result = list.stream()
            .flatMap(s -> s.equals("b") ? Stream.of(s, "d") : Stream.of(s))
            .collect(Collectors.toList());
        System.out.println("Stream API: " + result);
    }
    
    public static void main(String[] args) {
        wrongWay();
        methodIteratorRemove();
        methodCopy();
        methodCopyOnWrite();
        methodStream();
    }
}

Сравнение подходов

МетодБезопасностьПроизводительностьИспользование
Iterator.remove()ДаХорошаяУдаление элементов
Копия спискаДаМедленнееПростой случай
CopyOnWriteArrayListДаМедленно для записиМного читателей
Stream APIДаХорошаяФункциональный стиль

Ключевые выводы

  1. Никогда не модифицируйте список напрямую во время итерации — используйте Iterator.remove()
  2. Fail-fast механизм защищает от неопределённого поведения
  3. Для многопоточного кода используйте CopyOnWriteArrayList
  4. Stream API часто элегантнее для трансформаций
  5. ListIterator позволяет добавлять элементы безопасно
ConcurrentModificationException при итерации ArrayList | PrepBro