Избягване на ConcurrentModificationException в Java

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. Не се премахва по време на итерация

Ако искаме да запазим нашия цикъл за всеки , тогава можем. Просто трябва да изчакаме след итерацията, преди да премахнем елементите. Нека изпробваме това, като добавим това, което искаме да премахнем към списъка toRemove, докато итерираме:

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");

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

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

В тази статия показахме проблеми, с които можете да се сблъскате, ако премахвате елементи от колекция по време на итерация, а също така предоставихме някои решения за отричане на проблема.

Изпълнението на тези примери може да бъде намерено в GitHub. Това е проект на Maven, така че трябва да се изпълнява лесно, както е.