Избежать ConcurrentModificationException в Java: удаление из ArrayList
Быстрый ответ
Для предохранения от ConcurrentModificationException
при использовании ArrayList
рекомендуется применять метод Iterator.remove()
при итерации. Этот подход обеспечивает безопасное удаление элементов.
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().equals("removeMe")) {
it.remove(); // Элемент "removeMe" безопасно удаляется!
}
}
Важно помнить: применение метода remove()
у итератора аналогично действию стража, предотвращающего появление проблем с ConcurrentModificationException.
Способы предотвращения ConcurrentModificationException
Рассмотрим подходы, которые помогут избегнуть подобных исключений при управлении ArrayList
.
Условное удаление с помощью removeIf
(Java 8+)
Метод removeIf
используется с предикатом для условного удаления, удаляя ненужные элементы, будто пылесос.
list.removeIf(element -> element.equals("removeMe")); // Условное удаление выполняется легко и мягко.
Итерация в обратном порядке
С помощью классического цикла for
производите итерацию списка с конца, подобно возвращению киноленты назад. Таким образом индексы остаются под контролем и сохранены.
for (int i = list.size() – 1; i >= 0; i--) {
if (list.get(i).equals("removeMe")) {
list.remove(i); // Идем в обратном направлении для сохранения стабильности индексов.
}
}
Итерация по копии для безопасного удаления
Создайте копию списка для итерации и выполняйте удаление элементов в оригинале. Так копия остается неизменной.
ArrayList<String> tempList = new ArrayList<>(list);
for (String item : tempList) {
if (item.equals("removeMe")) {
list.remove(item); // Удаление исполняется в оригинальном списке, в то время как копия остается нетронутой.
}
}
Потокобезопасная итерация с помощью CopyOnWriteArrayList
CopyOnWriteArrayList
— это потокобезопасная версия ArrayList
, которая при каждой модификации создает новый массив. Данный метод обходится дороже, но он обеспечивает безопасность.
CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>(list);
for (String item : cowList) {
if (item.equals("removeMe")) {
cowList.remove(item); // Нет сюрпризов: оригинальный список не меняется во время итерации.
}
}
Визуализация
Вообразите процесс удаления элементов из ArrayList
как процедуру высадки пассажиров из поезда,
Изначально: 🚂👩👨🧒👵👨👩
Методы борьбы с ConcurrentModificationException:
Используйте кондуктора (Iterator):
Iterator<Person> it = train.iterator(); while (it.hasNext()) { Person p = it.next(); if (p.shouldBeRemoved()) { it.remove(); // Кондуктор надежно управляет процедурой высадки пассажиров } }
В результате: 🚂👩🧒👩
Или используйте организованные остановки (CopyOnWriteArrayList):
CopyOnWriteArrayList<Person> safeTrain = new CopyOnWriteArrayList<>(train); for (Person p : safeTrain) { if (p.shouldBeRemoved()) { safeTrain.remove(p); // Пассажиры спокойно выходят на остановках } }
В результате: 🚂👩🧒👩
Оба подхода приносят порядок в процесс, который без них был бы хаотичным.
Углубленно рассматриваем обработку конкурентного изменения
Мы привели основные решения, однако существуют и прочие методы борьбы с ConcurrentModificationException
.
Доступ к элементам по индексам
Наиболее текущего избегать итераций и прямо обращаться к элементам по индексам. Благодаря такому подходу удастся избежать непредвиденных исключений.
for (int i = 0; i < list.size(); i++) {
String item = list.get(i);
// Доступ без итераций предотвращает появление исключений.
}
Модификация перед или после итерации: путь рефакторинга
Изменяйте элементы до или после итерации, это позволит упростить логику и избежать конфликтов, связанных с изменениями.
Осознание поведения коллекций
Очень важно знать о последствиях изменений: например, удаление элемента приводит к смещению последующих элементов влево и изменяет их индексы.
Ключевые моменты, которые стоит принять к сведению
Есть несколько важных нюансов, которые стоит помнить перед началом работы:
CopyOnWriteArrayList: амбивалентный инструмент?
Применение CopyOnWriteArrayList
влечет за собой дополнительные затраты из-за создания копии массива при каждой записи. Его следует использовать с осмотрительностью.
Значение изменения размера при работе со списком
Будьте внимательны: с каждым удалением размер списка уменьшается. Всегда учитывайте это при использовании индексированных циклов.
Полезные ссылки
- ConcurrentModificationException (Java Platform SE 8) – Основная информация для понимания
ConcurrentModificationException
. - Почему возникает ConcurrentModificationException и как его отлаживать – Stack Overflow – Обсуждение сообществом поможет глубже понять ConcurrentModificationException.
- Быстрые и безопасные итераторы в Java – GeeksforGeeks – Характеризуем различные аспекты работы итераторов в Java.
- Руководство по Java 8 Stream – winterbe – Эффективное использование коллекций в многопоточном режиме.
- Обработка данных с помощью потоков Java SE 8, часть 1 – Подробное руководство Oracle по работе с потоками в Java 8 с примерами.