398 Stimmen

Wie vermeidet man die "ConcurrentModificationException", während man Elemente aus einem `ArrayList` entfernt, während man sie durchläuft?

Ich versuche, einige Elemente aus einem ArrayList zu entfernen, während ich es wie folgt durchlaufe:

for (String str : myArrayList) {
    if (someCondition) {
        myArrayList.remove(str);
    }
}

Natürlich erhalte ich eine ConcurrentModificationException, wenn ich versuche, Elemente aus der Liste zu entfernen, während ich gleichzeitig myArrayList durchlaufe. Gibt es eine einfache Lösung für dieses Problem?

633voto

arshajii Punkte 125945

Verwenden Sie einen Iterator und rufen Sie remove() auf:

Iterator iter = myArrayList.iterator();

while (iter.hasNext()) {
    String str = iter.next();

    if (someCondition)
        iter.remove();
}

221voto

Kevin DiTraglia Punkte 24902

Als Alternative zu den Antworten aller anderen habe ich immer etwas wie folgt gemacht:

List toRemove = new ArrayList();
for (String str : myArrayList) {
    if (someCondition) {
        toRemove.add(str);
    }
}
myArrayList.removeAll(toRemove);

Dies erspart Ihnen den direkten Umgang mit dem Iterator, erfordert jedoch eine weitere Liste. Aus irgendeinem Grund habe ich diesen Weg immer bevorzugt.

106voto

Mikhail Boyarsky Punkte 2771

Java 8 Benutzer können das tun: Liste.removeIf(...)

    List list = new ArrayList<>(Arrays.asList("a", "b", "c"));
    list.removeIf(e -> (someCondition));

Es werden Elemente in der Liste entfernt, für die someCondition erfüllt ist

70voto

Eric Stein Punkte 13189

Sie müssen die remove()-Methode des Iterators verwenden, was bedeutet, dass keine erweiterte for-Schleife verwendet werden kann:

for (final Iterator iterator = myArrayList.iterator(); iterator.hasNext(); ) {
    iterator.next();
    if (someCondition) {
        iterator.remove();
    }
}

42voto

Dima Naychuk Punkte 463

Nein, nein, NEIN!

In einzelnen Thread-Aufgaben brauchst du keinen Iterator zu verwenden, und erst recht kein CopyOnWriteArrayList (aufgrund der Leistungseinbußen).

Die Lösung ist viel einfacher: versuche, statt der for-each Schleife eine kanonische Schleife zu verwenden.

Laut den Eigentümern des Java-Copyrights (vor einigen Jahren Sun, jetzt Oracle) forEach-Schleifenanleitung verwendet sie einen Iterator, um durch die Sammlung zu gehen und verbirgt ihn nur, um den Code besser aussehen zu lassen. Aber leider, wie wir sehen können, hat es mehr Probleme als Gewinne produziert, sonst würde dieses Thema nicht aufkommen.

Zum Beispiel führt dieser Code zu einer java.util.ConcurrentModificationException, wenn er in die nächste Iteration auf einer modifizierten ArrayList eintritt:

        // Sammlung verarbeiten
        for (SomeClass currElement: testList) {

            SomeClass founDuplicate = findDuplicates(currElement);
            if (founDuplicate != null) {
                uniqueTestList.add(founDuplicate);
                testList.remove(testList.indexOf(currElement));
            }
        }

Aber der folgende Code funktioniert einwandfrei:

    // Sammlung verarbeiten
    for (int i = 0; i < testList.size(); i++) {
        SomeClass currElement = testList.get(i);

        SomeClass founDuplicate = findDuplicates(currElement);
        if (founDuplicate != null) {
            uniqueTestList.add(founDuplicate);
            testList.remove(testList.indexOf(currElement));
            i--; // um das Überspringen des verschobenen Elements zu vermeiden
        }
    }

Versuche also, den Indexansatz für das Iterieren über Sammlungen zu verwenden und vermeide die for-each-Schleife, da sie nicht äquivalent sind! Die for-each-Schleife verwendet interne Iteratoren, die die Sammlungsmodifikation überprüfen und eine ConcurrentModificationException auslösen. Um dies zu bestätigen, werfen Sie einen genaueren Blick auf den gedruckten Stack-Trace, wenn Sie das erste Beispiel verwenden, das ich gepostet habe:

Ausnahme im Thread "main" java.util.ConcurrentModificationException
    bei java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
    bei java.util.AbstractList$Itr.next(AbstractList.java:343)
    bei TestFail.main(TestFail.java:43)

Für Multithreading verwenden Sie entsprechende Mehrfachaufgabenansätze (wie das synchronized-Schlüsselwort).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X