Избежать 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 с примерами.