ПРИХОДИТЕ УЧИТЬСЯ НОВОЙ ПРОФЕССИИ ЛЕТОМ СО СКИДКОЙ ДО 70%Забронировать скидку

Избежать ConcurrentModificationException в Java: удаление из ArrayList

Пройдите тест, узнайте какой профессии подходите и получите бесплатную карьерную консультацию
В конце подарим скидку до 55% на обучение
Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

Для предохранения от ConcurrentModificationException при использовании ArrayList рекомендуется применять метод Iterator.remove() при итерации. Этот подход обеспечивает безопасное удаление элементов.

Java
Скопировать код
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("removeMe")) {
        it.remove();  // Элемент "removeMe" безопасно удаляется!
    }
}

Важно помнить: применение метода remove() у итератора аналогично действию стража, предотвращающего появление проблем с ConcurrentModificationException.

Пройдите тест и узнайте подходит ли вам сфера IT
Пройти тест

Способы предотвращения ConcurrentModificationException

Рассмотрим подходы, которые помогут избегнуть подобных исключений при управлении ArrayList.

Условное удаление с помощью removeIf (Java 8+)

Метод removeIf используется с предикатом для условного удаления, удаляя ненужные элементы, будто пылесос.

Java
Скопировать код
list.removeIf(element -> element.equals("removeMe")); // Условное удаление выполняется легко и мягко.

Итерация в обратном порядке

С помощью классического цикла for производите итерацию списка с конца, подобно возвращению киноленты назад. Таким образом индексы остаются под контролем и сохранены.

Java
Скопировать код
for (int i = list.size() – 1; i >= 0; i--) {
    if (list.get(i).equals("removeMe")) {
        list.remove(i); // Идем в обратном направлении для сохранения стабильности индексов.
    }
}

Итерация по копии для безопасного удаления

Создайте копию списка для итерации и выполняйте удаление элементов в оригинале. Так копия остается неизменной.

Java
Скопировать код
ArrayList<String> tempList = new ArrayList<>(list);
for (String item : tempList) {
    if (item.equals("removeMe")) {
        list.remove(item); // Удаление исполняется в оригинальном списке, в то время как копия остается нетронутой.
    }
}

Потокобезопасная итерация с помощью CopyOnWriteArrayList

CopyOnWriteArrayList — это потокобезопасная версия ArrayList, которая при каждой модификации создает новый массив. Данный метод обходится дороже, но он обеспечивает безопасность.

Java
Скопировать код
CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>(list);
for (String item : cowList) {
    if (item.equals("removeMe")) {
        cowList.remove(item); // Нет сюрпризов: оригинальный список не меняется во время итерации.
    }
}

Визуализация

Вообразите процесс удаления элементов из ArrayList как процедуру высадки пассажиров из поезда,

Изначально: 🚂👩👨🧒👵👨👩

Методы борьбы с ConcurrentModificationException:

  1. Используйте кондуктора (Iterator):

    Java
    Скопировать код
    Iterator<Person> it = train.iterator();
    while (it.hasNext()) {
        Person p = it.next();
        if (p.shouldBeRemoved()) {
            it.remove(); // Кондуктор надежно управляет процедурой высадки пассажиров
        }
    }

    В результате: 🚂👩🧒👩

  2. Или используйте организованные остановки (CopyOnWriteArrayList):

    Java
    Скопировать код
    CopyOnWriteArrayList<Person> safeTrain = new CopyOnWriteArrayList<>(train);
    for (Person p : safeTrain) {
        if (p.shouldBeRemoved()) {
            safeTrain.remove(p); // Пассажиры спокойно выходят на остановках
        }
    }

    В результате: 🚂👩🧒👩

Оба подхода приносят порядок в процесс, который без них был бы хаотичным.

Углубленно рассматриваем обработку конкурентного изменения

Мы привели основные решения, однако существуют и прочие методы борьбы с ConcurrentModificationException.

Доступ к элементам по индексам

Наиболее текущего избегать итераций и прямо обращаться к элементам по индексам. Благодаря такому подходу удастся избежать непредвиденных исключений.

Java
Скопировать код
for (int i = 0; i < list.size(); i++) {
    String item = list.get(i);
    // Доступ без итераций предотвращает появление исключений.
}

Модификация перед или после итерации: путь рефакторинга

Изменяйте элементы до или после итерации, это позволит упростить логику и избежать конфликтов, связанных с изменениями.

Осознание поведения коллекций

Очень важно знать о последствиях изменений: например, удаление элемента приводит к смещению последующих элементов влево и изменяет их индексы.

Ключевые моменты, которые стоит принять к сведению

Есть несколько важных нюансов, которые стоит помнить перед началом работы:

CopyOnWriteArrayList: амбивалентный инструмент?

Применение CopyOnWriteArrayList влечет за собой дополнительные затраты из-за создания копии массива при каждой записи. Его следует использовать с осмотрительностью.

Значение изменения размера при работе со списком

Будьте внимательны: с каждым удалением размер списка уменьшается. Всегда учитывайте это при использовании индексированных циклов.

Полезные ссылки

  1. ConcurrentModificationException (Java Platform SE 8)Основная информация для понимания ConcurrentModificationException.
  2. Почему возникает ConcurrentModificationException и как его отлаживать – Stack OverflowОбсуждение сообществом поможет глубже понять ConcurrentModificationException.
  3. Быстрые и безопасные итераторы в Java – GeeksforGeeks – Характеризуем различные аспекты работы итераторов в Java.
  4. Руководство по Java 8 Stream – winterbe – Эффективное использование коллекций в многопоточном режиме.
  5. Обработка данных с помощью потоков Java SE 8, часть 1Подробное руководство Oracle по работе с потоками в Java 8 с примерами.