Рубрики
Без рубрики

Избегание исключения ConcurrentModificationException в Java

Краткое представление об исключении ConcurrentModificationException Java, почему это происходит и как этого избежать.

Автор оригинала: baeldung.

1. введение

В этой статье мы рассмотрим класс ConcurrentModificationException |/.

Сначала мы объясним, как это работает, а затем докажем это с помощью теста для его запуска.

Наконец, мы попробуем некоторые обходные пути, используя практические примеры.

2. Запуск исключения ConcurrentModificationException

По сути, исключение ConcurrentModificationException используется для быстрого сбоя, когда что-то, над чем мы выполняем итерацию, изменяется. Давайте докажем это с помощью простого теста:

@Test(expected = ConcurrentModificationException.class)
public void whilstRemovingDuringIteration_shouldThrowException() throws InterruptedException {

    List integers = newArrayList(1, 2, 3);

    for (Integer integer : integers) {
        integers.remove(1);
    }
}

Как мы видим, перед завершением нашей итерации мы удаляем элемент. Это то, что вызывает исключение.

3. Решения

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

3.1. Использование итератора напрямую

В для каждого цикла используется итератор за кулисами, но он менее подробен. Однако, если мы изменим наш предыдущий тест, чтобы использовать итератор , у нас будет доступ к дополнительным методам, таким как remove(). Давайте попробуем использовать этот метод для изменения нашего списка:

for (Iterator iterator = integers.iterator(); iterator.hasNext();) {
    Integer integer = iterator.next();
    if(integer == 2) {
        iterator.remove();
    }
}

Теперь мы заметим, что нет никаких исключений. Причина этого заключается в том, что метод remove() не вызывает исключения ConcurrentModificationException. Безопасно звонить во время итерации.

3.2. Не Удалять Во Время Итерации

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

List integers = newArrayList(1, 2, 3);
List toRemove = newArrayList();

for (Integer integer : integers) {
    if(integer == 2) {
        toRemove.add(integer);
    }
}
integers.removeAll(toRemove);

assertThat(integers).containsExactly(1, 3);

Это еще один эффективный способ обойти проблему.

3.3. Использование removeIf()

Java 8 ввела метод removeIf() в интерфейс Collection . Это означает, что если мы работаем с ним, мы можем использовать идеи функционального программирования для достижения тех же результатов снова:

List integers = newArrayList(1, 2, 3);

integers.removeIf(i -> i == 2);

assertThat(integers).containsExactly(1, 3);

Этот декларативный стиль предлагает нам наименьшее количество многословия. Однако, в зависимости от варианта использования, мы можем найти другие методы более удобными.

3.4. Фильтрация С Использованием Потоков

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

Collection integers = newArrayList(1, 2, 3);

List collected = integers
  .stream()
  .filter(i -> i != 2)
  .map(Object::toString)
  .collect(toList());

assertThat(collected).containsExactly("1", "3");

Мы сделали обратное нашему предыдущему примеру, предоставив предикат для определения элементов, которые следует включать, а не исключать. Преимущество заключается в том, что мы можем объединить другие функции вместе с удалением. В этом примере мы используем функциональную map (), , но при желании могли бы использовать еще больше операций.

4. Заключение

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

Реализацию этих примеров можно найти на GitHub . Это проект Maven, поэтому его должно быть легко запустить как есть.