1301 Stimmen

Iteration durch eine Sammlung, Vermeidung von ConcurrentModificationException beim Entfernen von Objekten in einer Schleife

Wir alle wissen, dass Sie Folgendes nicht tun können, weil ConcurrentModificationException :

for (Object i : l) {
    if (condition(i)) {
        l.remove(i);
    }
}

Aber das funktioniert offenbar manchmal, aber nicht immer. Hier ist ein spezieller Code:

public static void main(String[] args) {
    Collection<Integer> l = new ArrayList<>();

    for (int i = 0; i < 10; ++i) {
        l.add(4);
        l.add(5);
        l.add(6);
    }

    for (int i : l) {
        if (i == 5) {
            l.remove(i);
        }
    }

    System.out.println(l);
}

Das führt natürlich dazu, dass:

Exception in thread "main" java.util.ConcurrentModificationException

Auch wenn mehrere Threads dies nicht tun. Wie auch immer.

Was ist die beste Lösung für dieses Problem? Wie kann ich ein Element aus der Sammlung in einer Schleife entfernen, ohne diese Ausnahme auszulösen?

Ich verwende auch eine willkürliche Collection hier, nicht unbedingt ein ArrayList Sie können sich also nicht auf get .

1 Stimmen

Hinweis an die Leser: Lesen Sie unbedingt die docs.oracle.com/javase/tutorial/collections/interfaces/ kann es einen einfacheren Weg geben, um das zu erreichen, was Sie tun wollen.

1672voto

Bill K Punkte 61074

Iterator.remove() sicher ist, können Sie es wie folgt verwenden:

List<String> list = new ArrayList<>();

// This is a clever way to create the iterator and call iterator.hasNext() like
// you would do in a while-loop. It would be the same as doing:
//     Iterator<String> iterator = list.iterator();
//     while (iterator.hasNext()) {
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
    String string = iterator.next();
    if (string.isEmpty()) {
        // Remove the current element from the iterator and the list.
        iterator.remove();
    }
}

Beachten Sie, dass Iterator.remove() ist die einzige sichere Möglichkeit, eine Sammlung während der Iteration zu ändern; das Verhalten ist nicht spezifiziert, wenn die zugrunde liegende Sammlung geändert wird auf irgendeine andere Weise während die Iteration läuft.

Quelle: docs.oracle > Die Sammelschnittstelle


Ähnlich verhält es sich, wenn Sie eine ListIterator und wollen hinzufügen. Artikel können Sie ListIterator#add aus demselben Grund können Sie auch Iterator#remove  - es ist so konzipiert, dass es das zulässt.


In Ihrem Fall haben Sie versucht, aus einer Liste zu entfernen, aber die gleiche Einschränkung gilt, wenn Sie versuchen put in eine Map während der Iteration seines Inhalts.

21 Stimmen

Was ist, wenn Sie ein anderes Element als das in der aktuellen Iteration zurückgegebene Element entfernen möchten?

2 Stimmen

Sie müssen .remove im Iterator verwenden und das kann nur das aktuelle Element entfernen, also nein :)

1 Stimmen

Seien Sie sich bewusst, dass dies langsamer ist im Vergleich zu ConcurrentLinkedDeque oder CopyOnWriteArrayList (zumindest in meinem Fall)

360voto

Claudiu Punkte 215027

Das funktioniert:

Iterator<Integer> iter = l.iterator();
while (iter.hasNext()) {
    if (iter.next() == 5) {
        iter.remove();
    }
}

Ich nahm an, dass, da eine foreach-Schleife ist syntaktischen Zucker für Iteration, mit einem Iterator würde nicht helfen ... aber es gibt Ihnen diese .remove() Funktionalität.

48 Stimmen

Foreach-Schleife est syntaktischer Zucker für die Iteration. Wie Sie jedoch bereits erwähnt haben, müssen Sie remove für den Iterator aufrufen - worauf Sie mit foreach keinen Zugriff haben. Das ist der Grund, warum Sie in einer foreach-Schleife nicht entfernen können (auch wenn Sie son tatsächlich einen Iterator unter der Haube verwenden)

36 Stimmen

+1 z.B. Code zur Verwendung von iter.remove() im Kontext, den die Antwort von Bill K nicht [direkt] hat.

243voto

assylias Punkte 308529

Mit Java 8 können Sie die neue removeIf Methode . Angewandt auf Ihr Beispiel:

Collection<Integer> coll = new ArrayList<>();
//populate

coll.removeIf(i -> i == 5);

4 Stimmen

Ooooo! Ich hatte gehofft, dass etwas in Java 8 oder 9 helfen könnte. Das scheint mir immer noch ziemlich langatmig zu sein, aber ich mag es trotzdem.

0 Stimmen

Wird die Implementierung von equals() auch in diesem Fall empfohlen?

1 Stimmen

Übrigens removeIf verwendet Iterator y while Schleife. Sie können es unter java 8 sehen java.util.Collection.java

44voto

Ashish Punkte 2970

Da die Frage bereits beantwortet wurde, d. h. der beste Weg ist, die remove-Methode des Iterator-Objekts zu verwenden, würde ich auf die Einzelheiten der Stelle eingehen, an der der Fehler "java.util.ConcurrentModificationException" geworfen wird.

Jede Sammlungsklasse hat eine private Klasse, die die Iterator-Schnittstelle implementiert und Methoden wie next() , remove() y hasNext() .

Der Code für next sieht in etwa so aus...

public E next() {
    checkForComodification();
    try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
    } catch(IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
    }
}

Hier die Methode checkForComodification ist implementiert als

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

Wenn Sie also explizit versuchen, ein Element aus der Sammlung zu entfernen, werden Sie sehen. Das Ergebnis ist modCount anders werden als expectedModCount , was zu der Ausnahme ConcurrentModificationException .

1 Stimmen

Sehr interessant. Ich danke Ihnen! Ich rufe remove() oft nicht selbst auf, sondern bevorzuge es, die Sammlung zu löschen, nachdem ich sie durchlaufen habe. Nicht zu sagen, dass das ein gutes Muster ist, nur was ich in letzter Zeit getan habe.

28voto

RodeoClown Punkte 12820

Sie können entweder den Iterator direkt verwenden, wie Sie erwähnt haben, oder eine zweite Auflistung behalten und jedes Element, das Sie entfernen möchten, der neuen Auflistung hinzufügen, dann removeAll am Ende. Auf diese Weise können Sie die Typsicherheit der for-each-Schleife weiterhin nutzen, allerdings auf Kosten von mehr Speicherplatz und Rechenzeit (was kein großes Problem sein sollte, es sei denn, Sie haben sehr, sehr große Listen oder einen sehr alten Computer)

public static void main(String[] args)
{
    Collection<Integer> l = new ArrayList<Integer>();
    Collection<Integer> itemsToRemove = new ArrayList<>();
    for (int i=0; i < 10; i++) {
        l.add(Integer.of(4));
        l.add(Integer.of(5));
        l.add(Integer.of(6));
    }
    for (Integer i : l)
    {
        if (i.intValue() == 5) {
            itemsToRemove.add(i);
        }
    }

    l.removeAll(itemsToRemove);
    System.out.println(l);
}

7 Stimmen

Dies ist, was ich normalerweise tun, aber die explizite Iterator ist eine elegantere Lösung, die ich denke.

1 Stimmen

Solange Sie nichts anderes mit dem Iterator machen, ist das in Ordnung. Wenn er offen liegt, ist es einfacher, Dinge wie den Aufruf von .next() zweimal pro Schleife usw. zu tun. Nicht ein großes Problem, aber kann Probleme verursachen, wenn Sie etwas komplizierter als nur durch eine Liste laufen, um Einträge zu löschen tun.

0 Stimmen

@RodeoClown: in der ursprünglichen Frage, Claudiu ist das Entfernen von der Sammlung, nicht der Iterator.

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