„Ich will ein Element aus einer Liste in einer Schleife löschen – und bekomme eine ConcurrentModificationException.“ Kommt dir bekannt vor?
\nWenn du schon einmal in Java programmiert und versucht hast, ein Element aus einer ArrayList oder HashMap während einer Iteration (z. B. in einer for-each-Schleife) zu entfernen, hast du diese furchtbare rote Exception bestimmt gesehen. Sie taucht im ungünstigsten Moment auf, als ob Java dir sagen würde: „Hey, was machst du da? Fass die Collection nicht an, während ich sie durchlaufe!“
Auf Stack Overflow hat diese Frage tausende Aufrufe. Und das nicht ohne Grund: Das Problem scheint einfach, aber unter der Haube steckt eine ganze Reihe von Optimierungen und Nuancen der Nebenläufigkeit. In diesem Artikel schauen wir uns an, wie man Objekte in einer Java-Schleife korrekt aus einer Collection löscht, ohne eine ConcurrentModificationException auszulösen – und ohne dabei an Leistung einzubüßen.
Los geht’s!
\n\nWarum tritt die ConcurrentModificationException auf?
\nKurze Antwort: Fail-Fast-Iteratoren. Die meisten Collections aus dem Java Collections Framework (ArrayList, HashSet, HashMap usw.) verwenden einen internen Änderungszähler (modCount). Wenn du einen Iterator erstellst, merkt er sich den aktuellen Wert von modCount. Bei jedem Aufruf von next() prüft der Iterator, ob die Collection von außen verändert wurde (über die Methoden add() oder remove() der Collection selbst). Wenn der Zähler nicht mehr übereinstimmt, fliegt die Exception.
Ein Codebeispiel, das garantiert abstürzt:
\nList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));\nfor (String item : list) {\n if ("B".equals(item)) {\n list.remove(item); // ConcurrentModificationException!\n }\n}\n\nWarum? Weil for-each implizit einen Iterator verwendet, während list.remove() den modCount direkt ändert, ohne den Iterator zu benachrichtigen. Das verzeiht der Iterator nicht.
Methode Nr. 1: Iterator.remove() verwenden – der Klassiker
\nDer einfachste und korrekteste Weg ist, den Iterator explizit zu nutzen und seine Methode remove() aufzurufen. Diese Methode synchronisiert den internen Zähler des Iterators mit der Collection.
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));\nIterator<String> iterator = list.iterator();\nwhile (iterator.hasNext()) {\n String item = iterator.next();\n if ("B".equals(item)) {\n iterator.remove(); // Sicher!\n }\n}\nSystem.out.println(list); // [A, C, D]\n\nVorteile: Sicher, funktioniert für alle Collections, die Iteratoren unterstützen. Nachteile: Etwas umständlich. Für einfache Filterungen gibt es modernere Ansätze.
\n\nMethode Nr. 2: Collection.removeIf() – modern und elegant
\nSeit Java 8 haben die meisten Collections die Methode removeIf(Predicate filter). Das ist der prägnanteste Weg, Elemente nach einer Bedingung zu löschen.
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));\nlist.removeIf(item -> "B".equals(item));\nSystem.out.println(list); // [A, C, D]\n\nIntern verwendet removeIf einen Iterator (oder einen direkten Durchlauf mit Indexspeicherung), sodass keine ConcurrentModificationException auftritt. Das ist die empfohlene Methode für einfache Fälle.
Leistung: Für ArrayList arbeitet removeIf in O(n), aber effizient – es verschiebt die Elemente in einem Durchlauf, anstatt sie einzeln zu löschen (was O(n²) wäre).
Methode Nr. 3: Rückwärts durchlaufen mit Index (für List)
\nWenn du aus irgendeinem Grund keinen Iterator verwenden kannst (z. B. weil du mehrere Elemente gleichzeitig ändern musst oder mit altem Code arbeitest), kannst du die Liste in umgekehrter Reihenfolge mit einer normalen for-Schleife und Index durchlaufen.
List<String> list = new A