Устраняем ConcurrentModificationException в HashMap Java

Пройдите тест, узнайте какой профессии подходите

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

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

Java
Скопировать код
// Для корректного удаления элементов применяйте метод remove() у итератора
for (Iterator<Map.Entry<KeyType, ValueType>> it = hashMap.entrySet().iterator(); it.hasNext();) {
    // Удаление следует выполнять осторожно, предварительно проверив условие для удаления элемента
    if (shouldRemove(it.next().getKey())) it.remove();
}

Для безопасной итерации по HashMap с последующим удалением элементов без возникновения ConcurrentModificationException, применяйте Iterator из метода entrySet(). Вызывайте it.remove() сразу после it.next().

Кинга Идем в IT: пошаговый план для смены профессии

Подробное объяснение

Использование Iterator для управляемого и последовательного удаления

Изменение содержимого HashMap в процессе его итерации может вызвать ConcurrentModificationException. Именно тут Iterator предлагает надёжный метод просмотра и удаления элементов по мере перебора.

Java
Скопировать код
// Пройдясь по "площади" HashMap, не забывайте о корректном удалении элементов!
Iterator<Map.Entry<KeyType, ValueType>> it = hashMap.entrySet().iterator();
while(it.hasNext()) {
    Map.Entry<KeyType, ValueType> entry = it.next();
    // Внимательность при удалении избавит вас от головной боли в процессе отладки!
    if (shouldRemove(entry.getKey())) it.remove();
}

Использование removeIf и лямбда-выражений

С появлением Java 8 наш код начал смотреться проще и стал интуитивно понятнее. Метод removeIf в сочетании с лямбда-выражениями позволяет удалять элементы, основываясь на предикате, быстро и лаконично:

Java
Скопировать код
// Очищаем hashMap от значений, которые не учитывают регистр и равны "Sample"
hashMap.entrySet().removeIf(entry -> "Sample".equalsIgnoreCase(entry.getValue()));

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

Представьте процесс итерации и удаления элементов как проход по ряду домино, где вы, по собственному выбору, опрокидываете отдельные костяшки, не запуская цепную реакцию:

Markdown
Скопировать код
| Ряд домино     | Действие   |
| -------------- | ---------  |
| 🟨🟦🟥🟩🟪       | Старт      |
| 🟨🚶‍♂️🟥🟩🟪     | Итерация   |
| 🟨❌🟥🟩🟪       | Отметка    |
| 🟨🟥🟩🟪        | Удаление   |

Асинхронное удаление элементов может запустить домино-эффект:

Markdown
Скопировать код
| Итерация                 | Синхронизация изменений |
| ----------------------   | ----------------------  |
| 🚶‍♂️ (🟦) 🟥🟩🟪           | ✅ Удаление без проблем |
| 🚶‍♂️ (Удалить 🟥 / ❌) 🟩🟪 | 🚫 Цепная реакция!      |

Iterator защищает вас от таких нежданных ситуаций:

Java
Скопировать код
Iterator<Map.Entry<Key, Value>> it = hashMap.entrySet().iterator();
while(it.hasNext()) {
    Map.Entry<Key, Value> entry = it.next();
    if(shouldRemove(entry.getKey())) {
        it.remove(); // 🛠️ Удаляем эломент, не разрушая порядок в ряду
    }
}

Эффективное удаление с использованием removeIf и лямбда-выражений

Лямбда-выражения, появившиеся в Java 8, существенно упростили процесс удаления неподходящих элементов. Вы можете проверять условия в любом месте итерации, что обеспечивает более элегантный и фокусированный код:

Java
Скопировать код
// Одним движением удаляем непригодные элементы
hashMap.entrySet().removeIf(entry -> entry.getKey().hasBadSmell());

Итерация и фильтрация с помощью потоков в Java

Потоки в Java 8 предназначены для тех, кто предпочитает функциональный подход к программированию. Они обеспечивают удобство фильтрации и сбора элементов, исключая нежелательные ключи:

Java
Скопировать код
// Гэндальф манипуляции с ненужными ключами: "Вам сюда не пройти!"
hashMap = hashMap.entrySet().stream()
    .filter(entry -> !unwantedKeys.contains(entry.getKey()))
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

Заметим, что это создаст новый HashMap, что может быть не наилучшим решением для большого объема данных.

Полезные материалы

  1. Iterator (Java Platform SE 8) — Изучите метод Iterator.remove() для лучшего понимания.
  2. Calling remove in foreach loop in Java – Stack Overflow — Нужны примеры? Обсуждение Iterator.remove() на Stack Overflow — ваш источник информации.
  3. ConcurrentModificationException (Java Platform SE 7) — Изучите основные проблемы, чтобы избежать ошибок при модификации коллекции «на ходу».
  4. Java Practices->Ways of iterating — Разнообразие способов итерации — это хорошо! Руководство по различным методам итерации по коллекциям в Java.
  5. Why is a ConcurrentModificationException thrown and how to debug it – Stack Overflow — Разъяснение ConcurrentModificationException поможет вам избежать неожиданных ошибок в процессе отладки.
  6. Java – The HashMap Class — Знание HashMap — залог успешного программирования! Подробный урок об использовании HashMap в Java.
  7. Map.Entry interface in Java with example – GeeksforGeeks — Познакомьтесь с интерфейсом Map.Entry для удобства итерации и модификации map.
Свежие материалы