Как избежать ConcurrentModificationException?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как избежать ConcurrentModificationException
ConcurrentModificationException выбрасывается при попытке изменить коллекцию во время итерации по ней. Это защита от неопределённого поведения. Есть несколько надёжных способов избежать этого исключения.
Причина исключения
// ❌ Эта код выбросит ConcurrentModificationException
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");
for (String item : list) {
if (item.equals("banana")) {
list.remove(item); // ОШИБКА! Изменение во время итерации
}
}
// Даже это вызовет проблемы:
for (int i = 0; i < list.size(); i++) {
list.remove(i); // Изменение длины во время цикла
}
Способ 1: Использование Iterator.remove()
Это самый безопасный способ удаления элементов во время итерации:
// ✅ Правильно
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("banana")) {
iterator.remove(); // Безопасное удаление
}
}
System.out.println(list); // [apple, cherry]
Почему это работает:
// Iterator имеет свой счётчик и отслеживает изменения
public class ArrayList<E> extends AbstractList<E> {
private class Itr implements Iterator<E> {
int expectedModCount = modCount;
public E next() {
checkForComodification();
// ...
}
public void remove() {
// remove() обновляет expectedModCount
// поэтому исключение не выбросится
ArrayList.this.remove(lastRet);
expectedModCount = modCount; // Синхронизация
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
}
Способ 2: Создание новой коллекции
Создайте новую коллекцию с отфильтрованными элементами:
// ✅ Создание новой коллекции
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");
// Способ 1: Stream API
List<String> result = list.stream()
.filter(item -> !item.equals("banana"))
.collect(Collectors.toList());
list = result; // Или присвоить обратно
System.out.println(list); // [apple, cherry]
// Способ 2: removeAll
List<String> toRemove = Collections.singletonList("banana");
list.removeAll(toRemove); // Удаляет все элементы из списка
Способ 3: RemoveIf с predicate
Для удаления элементов используйте встроенный метод removeIf:
// ✅ Использование removeIf
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");
list.removeIf(item -> item.equals("banana"));
System.out.println(list); // [apple, cherry]
// removeIf интернально использует Iterator для безопасного удаления
public boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove(); // Внутри используется iterator.remove()
removed = true;
}
}
return removed;
}
Способ 4: Добавление элементов во время итерации
// ❌ Неправильно - выбросит исключение
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
for (String item : list) {
if (item.equals("a")) {
list.add("new"); // ConcurrentModificationException
}
}
// ✅ Правильно - создать новый список
List<String> toAdd = new ArrayList<>();nfor (String item : list) {
if (item.equals("a")) {
toAdd.add("new");
}
}
list.addAll(toAdd); // Добавляем после итерации
Способ 5: CopyOnWriteArrayList для concurrent операций
Для многопоточных сценариев используйте потокобезопасные коллекции:
// ✅ Для многопоточности
import java.util.concurrent.CopyOnWriteArrayList;
List<String> list = new CopyOnWriteArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");
// Можно безопасно итерировать и модифицировать из разных потоков
Thread thread1 = new Thread(() -> {
for (String item : list) {
System.out.println(item);
}
});
Thread thread2 = new Thread(() -> {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("banana")) {
iterator.remove();
}
}
});
Особенности CopyOnWriteArrayList:
- Копирует массив при каждом изменении (дорого)
- Итерирование не выбросит исключение
- Хорошо для частых чтений, редких изменений
Способ 6: ListIterator для более сложных операций
// ✅ ListIterator для добавления и удаления
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("banana")) {
iterator.remove(); // Удаление
iterator.add("orange"); // Добавление
}
}
System.out.println(list); // [apple, orange, cherry]
Практический пример: Фильтрация данных
public class SafeIterationExample {
// ❌ Опасно
public void unsafeFilter(List<User> users) {
for (User user : users) {
if (user.getAge() < 18) {
users.remove(user); // ConcurrentModificationException
}
}
}
// ✅ Вариант 1: Iterator
public void safeFilterWithIterator(List<User> users) {
Iterator<User> iterator = users.iterator();
while (iterator.hasNext()) {
User user = iterator.next();
if (user.getAge() < 18) {
iterator.remove();
}
}
}
// ✅ Вариант 2: removeIf
public void safeFilterWithRemoveIf(List<User> users) {
users.removeIf(user -> user.getAge() < 18);
}
// ✅ Вариант 3: Stream API
public List<User> safeFilterWithStream(List<User> users) {
return users.stream()
.filter(user -> user.getAge() >= 18)
.collect(Collectors.toList());
}
}
Чеклист для избежания ConcurrentModificationException
✓ Используйте iterator.remove() для удаления элементов
✓ Используйте removeIf() для удаления по условию
✓ Используйте Stream API для фильтрации
✓ Используйте CopyOnWriteArrayList в многопоточных сценариях
✓ Избегайте прямого вызова list.remove() во время итерации
✓ Для добавления элементов — создайте отдельный список
Предпочитайте функциональный стиль (streams) — он безопаснее и понятнее.