Warum erhalte ich in diesem Beispiel keine java.util.ConcurrentModificationException?

176

Hinweis: Mir ist die Iterator#remove()Methode bekannt.

Im folgenden Codebeispiel verstehe ich nicht, warum die List.removein- mainMethode ausgelöst wird ConcurrentModificationException, aber nicht in der removeMethode.

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer toRemove) {
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer toRemove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }
}
Bhesh Gurung
quelle
3
Die einzige sichere Möglichkeit, ein Element aus einer Liste zu entfernen, während Sie über diese Liste iterieren, ist die Verwendung Iterator#remove(). Warum machst du das so?
Matt Ball
@ MattBall: Ich habe nur versucht zu sehen, was der Grund hier sein könnte. Weil es in beiden Methoden dieselbe "Enhanced for Loop" ist, aber eine wirft die ConcurrentModificationExceptionund die andere nicht.
Bhesh Gurung
Es gibt einen Unterschied in dem Element, das Sie entfernen. Bei der Methode entfernen Sie das 'mittlere Element'. In der Hauptsache entfernen Sie die letzten. Wenn Sie die Zahlen tauschen, erhalten Sie die Ausnahme in Ihrer Methode. Ich bin mir immer noch nicht sicher, warum das so ist.
Ben van Gompel
Ich hatte ein ähnliches Problem, als meine Schleife auch eine Position wiederholte, die nicht vorhanden war, nachdem ich ein Element in der Schleife entfernt hatte. Ich habe dies einfach durch Hinzufügen eines return;in die Schleife behoben .
Frank17
Unter Java8 Android würde das Entfernen eines anderen Elements als des letzten die ConcurrentModificationException auslösen. In Ihrem Fall würde die Funktion zum Entfernen eine Ausnahme ergeben, die entgegengesetzt ist, wie Sie zuvor beobachtet haben.
Gonglong

Antworten:

262

Hier ist der Grund: Wie es im Javadoc heißt:

Die von den Iterator- und ListIterator-Methoden dieser Klasse zurückgegebenen Iteratoren sind ausfallsicher: Wenn die Liste zu irgendeinem Zeitpunkt nach dem Erstellen des Iterators strukturell geändert wird, löst der Iterator eine ConcurrentModificationException aus, außer durch die eigenen Iterator-Methoden zum Entfernen oder Hinzufügen.

Diese Überprüfung erfolgt in der next()Methode des Iterators (wie Sie am Stacktrace sehen können). Wir werden die next()Methode jedoch nur erreichen, wenn hasNext()sie true geliefert wird. Dies wird von jedem aufgerufen, um zu überprüfen, ob die Grenze erreicht ist. hasNext()Wenn Sie in Ihrer Methode remove prüfen, ob ein anderes Element zurückgegeben werden muss, werden zwei Elemente zurückgegeben. Nachdem ein Element entfernt wurde, enthält die Liste nur noch zwei Elemente. Also ist alles pfirsichfarben und wir sind mit dem Iterieren fertig. Die Prüfung auf gleichzeitige Änderungen findet nicht statt, da dies in der next()Methode erfolgt, die niemals aufgerufen wird.

Als nächstes kommen wir zur zweiten Schleife. Nachdem wir die zweite Zahl entfernt haben, prüft die hasNext-Methode erneut, ob weitere Werte zurückgegeben werden können. Es wurden bereits zwei Werte zurückgegeben, aber die Liste enthält nur noch einen. Aber der Code hier ist:

public boolean hasNext() {
        return cursor != size();
}

1! = 2, also fahren wir mit der next()Methode fort, die nun erkennt, dass jemand mit der Liste herumgespielt hat, und die Ausnahme auslöst.

Hoffe das klärt deine Frage.

Zusammenfassung

List.remove()wird nicht geworfen, ConcurrentModificationExceptionwenn das vorletzte Element aus der Liste entfernt wird.

aufdringlich
quelle
5
@pushy: Nur Antworten, die zu beantworten scheinen, was die Frage tatsächlich stellt, und die Erklärung ist gut. Ich akzeptiere diese Antwort und auch +1. Vielen Dank.
Bhesh Gurung
42

Eine Möglichkeit, damit umzugehen, besteht darin Collection, gegebenenfalls etwas aus einer Kopie einer (nicht der Sammlung selbst) zu entfernen . Clonedie ursprüngliche Sammlung es, um eine Kopie über eine zu machen Constructor.

Diese Ausnahme kann durch Methoden ausgelöst werden, die eine gleichzeitige Änderung eines Objekts festgestellt haben, wenn eine solche Änderung nicht zulässig ist.

Zunächst einmal denke ich, dass finales für Ihren speziellen Fall kein guter Weg ist, wenn Sie beabsichtigen, die Liste nach der Deklaration zu ändern

private static final List<Integer> integerList;

Ziehen Sie auch in Betracht, eine Kopie anstelle der ursprünglichen Liste zu ändern.

List<Integer> copy = new ArrayList<Integer>(integerList);

for(Integer integer : integerList) {
    if(integer.equals(remove)) {                
        copy.remove(integer);
    }
}
James Raitsev
quelle
14

Die Forward / Iterator-Methode funktioniert beim Entfernen von Elementen nicht. Sie können das Element ohne Fehler entfernen, es wird jedoch ein Laufzeitfehler angezeigt, wenn Sie versuchen, auf entfernte Elemente zuzugreifen. Sie können den Iterator nicht verwenden, da er, wie aufdringlich zeigt, eine ConcurrentModificationException verursacht. Verwenden Sie stattdessen eine reguläre for-Schleife, aber gehen Sie rückwärts durch.

List<Integer> integerList;
integerList = new ArrayList<Integer>();
integerList.add(1);
integerList.add(2);
integerList.add(3);

int size= integerList.size();

//Item to remove
Integer remove = Integer.valueOf(3);

Eine Lösung:

Durchlaufen Sie das Array in umgekehrter Reihenfolge, wenn Sie ein Listenelement entfernen möchten. Wenn Sie die Liste einfach rückwärts durchgehen, vermeiden Sie den Besuch eines entfernten Elements, wodurch die Ausnahme beseitigt wird.

//To remove items from the list, start from the end and go backwards through the arrayList
//This way if we remove one from the beginning as we go through, then we will avoid getting a runtime error
//for java.lang.IndexOutOfBoundsException or java.util.ConcurrentModificationException as when we used the iterator
for (int i=size-1; i> -1; i--) {
    if (integerList.get(i).equals(remove) ) {
        integerList.remove(i);
    }
}
RightHandedMonkey
quelle
Brilliante Idee !
dobrivoje
7

Dieses Snippet löst immer eine ConcurrentModificationException aus.

Die Regel lautet "Sie dürfen keine Elemente ändern (Elemente zur Liste hinzufügen oder daraus entfernen), während Sie mit einem Iterator darüber iterieren (was passiert, wenn Sie eine for-each-Schleife verwenden)".

JavaDocs:

Die von den Iterator- und ListIterator-Methoden dieser Klasse zurückgegebenen Iteratoren sind ausfallsicher: Wenn die Liste zu irgendeinem Zeitpunkt nach dem Erstellen des Iterators strukturell geändert wird, löst der Iterator eine ConcurrentModificationException aus, außer durch die eigenen Iterator-Methoden zum Entfernen oder Hinzufügen.

Wenn Sie also die Liste (oder eine Sammlung im Allgemeinen) ändern möchten, verwenden Sie den Iterator, da er die Änderungen kennt und diese daher ordnungsgemäß behandelt werden.

Hoffe das hilft.

Bhushan
quelle
3
Das OP stellt eindeutig fest, dass eine der Schleifen KEINE Ausnahme auslöst, und der Fragesteller war, warum dies passiert ist.
madth3
Was meinst du mit "fragend"?
Bhushan
4

Ich hatte das gleiche Problem, aber für den Fall, dass ich ein Element in die iterierte Liste einfügte. Ich habe es so gemacht

public static void remove(Integer remove) {
    for(int i=0; i<integerList.size(); i++) {
        //here is maybe fine to deal with integerList.get(i)==null
        if(integerList.get(i).equals(remove)) {                
            integerList.remove(i);
        }
    }
}

Jetzt geht alles in Ordnung, da Sie keinen Iterator über Ihre Liste erstellen, sondern "manuell" darüber iterieren. Und die Bedingung i < integerList.size()wird Sie niemals täuschen, denn wenn Sie etwas in die Listengröße des Listendekrements / -inkrements entfernen / hinzufügen.

Hoffe es hilft, für mich war das eine Lösung.

Gondil
quelle
Das ist nicht wahr ! Beweis: Führen Sie dieses Snippet aus, um das Ergebnis anzuzeigen: public static void main (String ... args) {List <String> listOfBooks = new ArrayList <> (); listOfBooks.add ("Code Complete"); listOfBooks.add ("Code 22"); listOfBooks.add ("22 Effective"); listOfBooks.add ("Netbeans 33"); System.err.println ("Vor dem Löschen:" + listOfBooks); for (int index = 0; index <listOfBooks.size (); index ++) {if (listOfBooks.get (index) .contains ("22")) {listOfBooks.remove (index); }} System.err.println ("Nach dem Löschen:" + listOfBooks); }
dobrivoje
1

Wenn Sie Copy-on-Write-Sammlungen verwenden, funktioniert dies. Wenn Sie jedoch list.iterator () verwenden, verweist der zurückgegebene Iterator immer auf die Auflistung von Elementen, wie sie war, als (wie unten) list.iterator () aufgerufen wurde, selbst wenn ein anderer Thread die Auflistung ändert. Alle Mutationsmethoden, die für einen Iterator oder ListIterator auf Copy-on-Write-Basis aufgerufen werden (z. B. Hinzufügen, Festlegen oder Entfernen), lösen eine UnsupportedOperationException aus.

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new CopyOnWriteArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}
JohnnyO
quelle
0

Dies läuft gut unter Java 1.6

~% javac RemoveListElementDemo.java
~% java RemoveListElementDemo
~% cat RemoveListElementDemo.java

import java.util.*;
public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}

~%

Battosai
quelle
Entschuldigung für den Tippfehler Dies läuft gut auf Java 1.6
Battosai
Hmm ... Vielleicht haben Sie eine andere Implementierung. Aber laut Spezifikation soll es das tun, IMO. Schauen Sie sich die Antwort von @ Pushy an.
Bhesh Gurung
leider nicht id auf java 1.8
dobrivoje
0

In meinem Fall habe ich es so gemacht:

int cursor = 0;
do {
    if (integer.equals(remove))
        integerList.remove(cursor);
    else cursor++;
} while (cursor != integerList.size());
Saif Hamed
quelle
0

Ändern Sie Iterator for eachin for loopzu lösen.

Und der Grund ist:

Die von den Iterator- und ListIterator-Methoden dieser Klasse zurückgegebenen Iteratoren sind ausfallsicher: Wenn die Liste zu irgendeinem Zeitpunkt nach dem Erstellen des Iterators strukturell geändert wird, löst der Iterator eine ConcurrentModificationException aus, außer durch die eigenen Iterator-Methoden zum Entfernen oder Hinzufügen.

- Referenzierte Java-Dokumente.

Stephen
quelle
-1

Überprüfen Sie Ihren Code Mann ....

In der Hauptmethode versuchen Sie, das 4. Element, das nicht vorhanden ist, und damit den Fehler zu entfernen. Bei der Methode remove () versuchen Sie, das dritte Element zu entfernen, das vorhanden ist, und daher keinen Fehler.

Abhishek
quelle
Sie irren sich: Die Zahlen 2und 3sind keine Indizes für die Liste, sondern Elemente. Beide Entfernungslogiken prüfen anhand equalsder Listenelemente, nicht anhand des Index der Elemente. Wenn es indexbezogen wäre IndexOutOfBoundsException, wäre dies nicht der Fall ConcurrentModificationException.
Malte Hartwig