В чем причина получения ConcurrentModificationException в ArrayList
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
ConcurrentModificationException в ArrayList
Это классическая ошибка при работе с коллекциями. ConcurrentModificationException выбрасывается, когда структура коллекции изменяется во время итерации по ней.
Основная причина
ArrayList хранит внутренний счётчик modCount, который увеличивается при любом изменении структуры коллекции (add, remove, clear). Во время итерации Iterator проверяет, не изменился ли modCount. Если изменился - выбрасывается исключение.
Самая распространённая ошибка
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
// ОШИБКА - ConcurrentModificationException
for (String item : list) {
if (item.equals("b")) {
list.remove(item); // Изменяем структуру!
}
}
Почему происходит исключение:
- for-each цикл использует Iterator
- list.remove() увеличивает modCount
- Iterator видит, что modCount изменился
- Выбросить ConcurrentModificationException
Удаление через iterator.remove() - правильно
// ПРАВИЛЬНО - не будет исключения
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String item = it.next();
if (item.equals("b")) {
it.remove(); // Это безопасно!
}
}
// Результат: ["a", "c"]
Почему это работает:
- Iterator.remove() корректно обновляет свой внутренний счётчик
- modCount и expectedModCount остаются синхронизированы
Многопоточность - реальная конкуренция
// ОШИБКА - реальная конкуренция
ArrayList<String> list = new ArrayList<>();
// Thread 1
for (String item : list) {
System.out.println(item);
}
// Thread 2 (одновременно)
list.add("new item"); // ConcurrentModificationException в Thread 1!
Решение - использовать CopyOnWriteArrayList:
// ПРАВИЛЬНО - потокобезопасно
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// Thread 1
for (String item : list) {
System.out.println(item); // Безопасно
}
// Thread 2
list.add("new item"); // Не вызовет исключение
Практические решения
Решение 1: Использовать Iterator.remove()
ArrayList<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String item = it.next();
if (item.contains("a")) {
it.remove(); // Безопасно
}
}
Решение 2: Создать новый список
ArrayList<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");
ArrayList<String> filtered = new ArrayList<>();
for (String item : list) {
if (!item.equals("banana")) {
filtered.add(item);
}
}
list = filtered;
Решение 3: Использовать removeIf() - ЛУЧШИЙ СПОСОБ
ArrayList<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");
list.removeIf(item -> item.equals("banana"));
// Результат: ["apple", "cherry"]
Это самый безопасный и читаемый способ!
Решение 4: Stream API
ArrayList<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");
list = list.stream()
.filter(item -> !item.equals("banana"))
.collect(Collectors.toCollection(ArrayList::new));
Внутреннее устройство ArrayList
private int modCount = 0; // Счётчик изменений
public boolean add(E e) {
modCount++; // Увеличить счётчик
// ...
}
// Iterator проверяет modCount
private class Itr implements Iterator<E> {
int expectedModCount = modCount;
public E next() {
checkForComodification();
// ...
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
Таблица безопасных операций
| Операция | Безопасна? | Почему |
|---|---|---|
| list.remove() в for-each | Нет | Меняет modCount |
| iterator.remove() | Да | Синхронизирует счётчики |
| list.removeIf() | Да | Встроенный механизм |
| Stream.filter() | Да | Новый список |
Лучшие практики
// Плохо
for (Item item : list) {
if (shouldRemove(item)) {
list.remove(item);
}
}
// Хорошо - используй removeIf
list.removeIf(item -> shouldRemove(item));
// Хорошо - используй iterator
Iterator<Item> it = list.iterator();
while (it.hasNext()) {
if (shouldRemove(it.next())) {
it.remove();
}
}
Заключение
ConcurrentModificationException выбрасывается, когда ArrayList изменяется во время итерации, потому что Iterator видит несоответствие между expectedModCount и текущим modCount.
Используй:
- removeIf() - для простых случаев
- Iterator.remove() - когда нужен контроль
- Stream API - для функционального стиля
- Избегай прямого удаления через list.remove() в циклах