Vermeiden Sie die ConcurrentModificationException in Java

1. Einführung

In diesem Artikel werfen wir einen Blick auf die ConcurrentModificationException -Klasse.

Zuerst erklären wir, wie es funktioniert, und beweisen es dann mit einem Test, um es auszulösen.

Zum Schluss probieren wir einige Problemumgehungen anhand praktischer Beispiele.

2. Auslösen einer ConcurrentModificationException

Im Wesentlichen wird die ConcurrentModificationException dazu verwendet, um ausfallsicher zu werden, wenn etwas geändert wird, für das wir eine Iteration durchführen. Lassen Sie uns dies mit einem einfachen Test beweisen:

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

    List<Integer> integers = newArrayList(1, 2, 3);

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

Wie wir sehen können, entfernen wir ein Element, bevor wir unsere Iteration beenden. Das ist, was die Ausnahme auslöst.

3. Lösungen

Manchmal möchten wir möglicherweise Elemente während einer Iteration aus einer Sammlung entfernen. Wenn dies der Fall ist, gibt es einige Lösungen.

3.1. Einen Iterator direkt verwenden

Eine for-each -Schleife verwendet einen Iterator hinter den Kulissen, ist jedoch weniger ausführlich. Wenn wir jedoch unseren vorherigen Test für die Verwendung eines Iterators überarbeitet haben, haben wir Zugriff auf zusätzliche Methoden wie remove (). Wir versuchen, diese Liste stattdessen mit unserer Methode zu ändern:

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

Nun werden wir feststellen, dass es keine Ausnahme gibt. Der Grund dafür ist, dass die remove () -Methode keine __ConcurrentModificationException verursacht.

3.2. Wird während der Iteration nicht entfernt

Wenn wir unsere for-each -Schleife beibehalten möchten, können wir dies tun. Es ist nur so, dass wir erst nach dem Iterieren warten müssen, bevor wir die Elemente entfernen. Lassen Sie uns dies ausprobieren, indem Sie das zu entfernende Element zu einer toRemove -Liste hinzufügen, während wir iterieren:

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

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

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

Dies ist eine weitere effektive Möglichkeit, das Problem zu umgehen.

3.3. RemoveIf () verwenden

Java 8 führte die removeIf () - Methode in die Collection -Schnittstelle ein.

Das heißt, wenn wir damit arbeiten, können wir mit Ideen der funktionalen Programmierung wieder dieselben Ergebnisse erzielen:

List<Integer> integers = newArrayList(1, 2, 3);

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

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

Dieser deklarative Stil bietet uns die geringste Menge an Ausführlichkeit. Je nach Anwendungsfall können andere Methoden jedoch günstiger sein.

3.4. Filtern mit Streams

Beim Eintauchen in die Welt der funktionalen/deklarativen Programmierung können wir das Ändern von Sammlungen vergessen, stattdessen können wir uns auf Elemente konzentrieren, die tatsächlich verarbeitet werden sollten:

Collection<Integer> integers = newArrayList(1, 2, 3);

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

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

Wir haben die Umkehrung zu unserem vorherigen Beispiel gemacht, indem wir ein Prädikat zur Bestimmung der Elemente bereitstellen, die einschließen, nicht ausschließen sollen Der Vorteil ist, dass wir neben der Entfernung auch andere Funktionen miteinander verketten können. In dem Beispiel verwenden wir eine funktionale map (), , könnten aber noch mehr Operationen verwenden, wenn wir möchten.

4. Fazit

In diesem Artikel wurden Probleme beschrieben, auf die Sie stoßen können, wenn Sie Elemente während einer Iteration aus einer Sammlung entfernen. Außerdem wurden einige Lösungen zur Behebung des Problems bereitgestellt.

Die Implementierung dieser Beispiele finden Sie unter https://github.com/eugenp/tutorials/tree/master/core-java-concurrency-collections [über GitHub Dies ist ein Maven-Projekt, daher sollte es leicht auszuführen sein, wie es ist.