So vermeiden Sie java.util.ConcurrentModificationException beim Durchlaufen und Entfernen von Elementen aus einer ArrayList

203

Ich habe eine ArrayList, über die ich iterieren möchte. Während ich darüber iteriere, muss ich gleichzeitig Elemente entfernen. Offensichtlich wirft dies eine java.util.ConcurrentModificationException.

Was ist die beste Vorgehensweise, um dieses Problem zu lösen? Soll ich zuerst die Liste klonen?

Ich entferne die Elemente nicht in der Schleife selbst, sondern in einem anderen Teil des Codes.

Mein Code sieht folgendermaßen aus:

public class Test() {
    private ArrayList<A> abc = new ArrayList<A>();

    public void doStuff() {
        for (A a : abc) 
        a.doSomething();
    }

    public void removeA(A a) {
        abc.remove(a);
    }
}

a.doSomethingkönnte anrufen Test.removeA();

Belphegor
quelle

Antworten:

325

Zwei Optionen:

  • Erstellen Sie eine Liste mit Werten, die Sie entfernen möchten, fügen Sie sie dieser Liste innerhalb der Schleife hinzu und rufen Sie originalList.removeAll(valuesToRemove)am Ende auf
  • Verwenden Sie die remove()Methode auf dem Iterator selbst. Beachten Sie, dass dies bedeutet, dass Sie die erweiterte for-Schleife nicht verwenden können.

Als Beispiel für die zweite Option entfernen Sie Zeichenfolgen mit einer Länge von mehr als 5 aus einer Liste:

List<String> list = new ArrayList<String>();
...
for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
    String value = iterator.next();
    if (value.length() > 5) {
        iterator.remove();
    }
}
Jon Skeet
quelle
2
Ich hätte erwähnen sollen, dass ich die Elemente in einem anderen Teil des Codes und nicht in der Schleife selbst entferne.
RoflcoptrException
@Roflcoptr: Nun, es ist schwer zu beantworten, ohne zu sehen, wie die beiden Codebits interagieren. Grundsätzlich kann man das nicht machen. Es ist nicht klar, ob das Klonen der Liste zuerst helfen würde, ohne zu sehen, wie alles zusammen hängt. Können Sie in Ihrer Frage weitere Details angeben?
Jon Skeet
Ich weiß, dass das Klonen der Liste helfen würde, aber ich weiß nicht, ob es ein guter Ansatz ist. Aber ich werde noch etwas Code hinzufügen.
RoflcoptrException
2
Diese Lösung führt auch zu java.util.ConcurrentModificationException, siehe stackoverflow.com/a/18448699/2914140 .
CoolMind
1
@CoolMind: Ohne mehrere Threads sollte dieser Code in Ordnung sein.
Jon Skeet
17

Aus den JavaDocs der ArrayList

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, außer durch die eigenen Iterator-Methoden zum Entfernen oder Hinzufügen der Iterator eine ConcurrentModificationException aus des Iterators.

Varun Achar
quelle
6
und wo ist die Antwort auf die Frage?
Adelin
Wie es heißt, außer durch die eigenen Methoden zum Entfernen oder Hinzufügen des Iterators
Varun Achar
14

Sie versuchen, den Wert aus der Liste in der erweiterten "for-Schleife" zu entfernen, was nicht möglich ist, selbst wenn Sie einen Trick anwenden (den Sie in Ihrem Code ausgeführt haben). Besser ist es, die Iterator-Ebene zu codieren, wie hier empfohlen.

Ich frage mich, wie die Leute keinen traditionellen Loop-Ansatz vorgeschlagen haben.

for( int i = 0; i < lStringList.size(); i++ )
{
    String lValue = lStringList.get( i );
    if(lValue.equals("_Not_Required"))
    {
         lStringList.remove(lValue);
         i--; 
    }  
}

Das funktioniert auch.

suhas0sn07
quelle
2
Das ist nicht richtig!!! Wenn Sie ein Element entfernen, nimmt das nächste seine Position ein, und während i erhöht, wird das nächste Element in der nächsten Iteration nicht überprüft. In diesem Fall sollten Sie sich für (int i = lStringList.size (); i> -1; i--)
Johntor
1
Zustimmen! Alternativ ist es, i-- durchzuführen; in if-Bedingung innerhalb der for-Schleife.
suhas0sn07
Ich denke, diese Antwort wurde bearbeitet, um die Probleme in den obigen Kommentaren zu beheben. So wie es jetzt ist, funktioniert es zumindest für mich gut.
Kira Resari
11

Sie sollten das Array wirklich nur auf herkömmliche Weise durchlaufen

Jedes Mal, wenn Sie ein Element aus der Liste entfernen, werden die nachfolgenden Elemente vorwärts verschoben. Solange Sie keine anderen Elemente als das iterierende ändern, sollte der folgende Code funktionieren.

public class Test(){
    private ArrayList<A> abc = new ArrayList<A>();

    public void doStuff(){
        for(int i = (abc.size() - 1); i >= 0; i--) 
            abc.get(i).doSomething();
    }

    public void removeA(A a){
        abc.remove(a);
    }
}
Marcus
quelle
10

In Java 8 können Sie die Sammlungsschnittstelle verwenden und dazu die Methode removeIf aufrufen:

yourList.removeIf((A a) -> a.value == 2);

Weitere Informationen finden Sie hier

ggeo
quelle
6

Führen Sie die Schleife wie gewohnt aus. Dies java.util.ConcurrentModificationExceptionist ein Fehler in Bezug auf die Elemente, auf die zugegriffen wird.

Also versuche:

for(int i = 0; i < list.size(); i++){
    lista.get(i).action();
}
Tacila
quelle
Sie haben dies vermieden, java.util.ConcurrentModificationExceptionindem Sie nichts von der Liste entfernt haben. Tricky. :) Sie können dies nicht wirklich "den normalen Weg" nennen, um eine Liste zu iterieren.
Zsolt Sky
6

Während Sie die Liste durchlaufen, ist es möglich, das Element zu entfernen. Lassen Sie unten meine Beispiele sehen,

ArrayList<String>  names = new ArrayList<String>();
        names.add("abc");
        names.add("def");
        names.add("ghi");
        names.add("xyz");

Ich habe die oben genannten Namen der Array-Liste. Und ich möchte den Namen "def" aus der obigen Liste entfernen,

for(String name : names){
    if(name.equals("def")){
        names.remove("def");
    }
}

Der obige Code löst die ConcurrentModificationException aus Ausnahme aus, da Sie die Liste während der Iteration ändern.

Um den Namen "def" auf diese Weise aus Arraylist zu entfernen,

Iterator<String> itr = names.iterator();            
while(itr.hasNext()){
    String name = itr.next();
    if(name.equals("def")){
        itr.remove();
    }
}

Mit dem obigen Code können wir über den Iterator den Namen "def" aus der Arrayliste entfernen und versuchen, das Array zu drucken. Die folgende Ausgabe wird angezeigt.

Ausgabe: [abc, ghi, xyz]

Indra K.
quelle
Andernfalls können wir eine gleichzeitige Liste verwenden, die im gleichzeitigen Paket verfügbar ist, sodass Sie während der Iteration Operationen zum Entfernen und Hinzufügen ausführen können. Siehe zum Beispiel das folgende Code-Snippet. ArrayList <String> names = new ArrayList <String> (); CopyOnWriteArrayList <String> copyNames = new CopyOnWriteArrayList <String> (Namen); for (String name: copyNames) {if (name.equals ("def")) {copyNames.remove ("def"); }}
Indra K
CopyOnWriteArrayList wird die teuerste Operation sein.
Indra K
5

Eine Möglichkeit besteht darin, die removeAMethode dahingehend zu ändern :

public void removeA(A a,Iterator<A> iterator) {
     iterator.remove(a);
     }

Dies würde jedoch bedeuten, dass Sie doSomething()in der Lage sein sollten, das iteratoran die removeMethode weiterzugeben . Keine sehr gute Idee.

Können Sie dies in zwei Schritten tun: Markieren Sie in der ersten Schleife, wenn Sie die Liste durchlaufen, anstatt die ausgewählten Elemente zu entfernen, diese als gelöscht . Dazu können Sie diese Elemente (flache Kopie) einfach in ein anderes kopieren List.

Sobald Ihre Iteration abgeschlossen ist, führen Sie einfach removeAllalle Elemente in der zweiten Liste aus der ersten Liste aus.

Bhaskar
quelle
Ausgezeichnet, ich habe den gleichen Ansatz verwendet, obwohl ich zweimal schleife. es macht die Dinge einfach und keine gleichzeitigen Probleme damit :)
Pankaj Nimgade
1
Ich sehe nicht, dass Iterator eine Methode zum Entfernen (a) hat. Das remove () akzeptiert keine Argumente docs.oracle.com/javase/8/docs/api/java/util/Iterator.html Was fehlt mir?
c0der
5

Hier ist ein Beispiel, in dem ich eine andere Liste verwende, um die zu entfernenden Objekte hinzuzufügen. Anschließend verwende ich stream.foreach, um Elemente aus der ursprünglichen Liste zu entfernen:

private ObservableList<CustomerTableEntry> customersTableViewItems = FXCollections.observableArrayList();
...
private void removeOutdatedRowsElementsFromCustomerView()
{
    ObjectProperty<TimeStamp> currentTimestamp = new SimpleObjectProperty<>(TimeStamp.getCurrentTime());
    long diff;
    long diffSeconds;
    List<Object> objectsToRemove = new ArrayList<>();
    for(CustomerTableEntry item: customersTableViewItems) {
        diff = currentTimestamp.getValue().getTime() - item.timestamp.getValue().getTime();
        diffSeconds = diff / 1000 % 60;
        if(diffSeconds > 10) {
            // Element has been idle for too long, meaning no communication, hence remove it
            System.out.printf("- Idle element [%s] - will be removed\n", item.getUserName());
            objectsToRemove.add(item);
        }
    }
    objectsToRemove.stream().forEach(o -> customersTableViewItems.remove(o));
}
serup
quelle
Ich denke, dass Sie zusätzliche Arbeit leisten, indem Sie zwei Schleifen ausführen. Im schlimmsten Fall gehören die Schleifen zur gesamten Liste. Wäre am einfachsten und kostengünstigsten in nur einer Schleife.
Luis Carlos
Ich glaube nicht, dass Sie Objekte aus der ersten Schleife entfernen können, daher ist eine zusätzliche Entfernungsschleife erforderlich. Außerdem ist die Entfernungsschleife nur Objekte zum Entfernen - vielleicht könnten Sie ein Beispiel mit nur einer Schleife schreiben, ich würde es gerne sehen - danke @ LuisCarlos
Serup
Wie Sie mit diesem Code sagen, können Sie kein Element innerhalb der for-Schleife entfernen, da dies die Ausnahme java.util.ConcurrentModificationException verursacht. Sie können jedoch eine Basis für verwenden. Hier schreibe ich ein Beispiel mit einem Teil Ihres Codes.
Luis Carlos
1
für (int i = 0; i <customersTableViewItems.size (); i ++) {diff = currentTimestamp.getValue (). getTime () - customersTableViewItems.get (i) .timestamp.getValue (). getTime (); diffSeconds = diff / 1000% 60; if (diffSeconds> 10) {customersTableViewItems.remove (i--); }} Ist wichtig, weil Sie kein Element überspringen möchten. Sie können auch die von der ArrayList-Klasse bereitgestellte Methode removeIf (Prädikatfilter <? Super E>) verwenden. Hoffe diese Hilfe
Luis Carlos
1
Die Ausnahme tritt auf, weil in for-Schleife dort als aktiver Verweis auf Iterator der Liste. Normalerweise gibt es keine Referenz und Sie haben mehr Flexibilität beim Ändern der Daten. Hoffe diese Hilfe
Luis Carlos
3

Verwenden Sie für jede Schleife normal für Schleife. Mit dem folgenden Code wird beispielsweise das gesamte Element in der Array-Liste entfernt, ohne dass eine java.util.ConcurrentModificationException angegeben wird. Sie können die Bedingung in der Schleife entsprechend Ihrem Anwendungsfall ändern.

   for(int i=0;i<abc.size();i++)  {

          e.remove(i);
        }
Shubham Chopra
quelle
2

Mach so etwas einfach:

for (Object object: (ArrayList<String>) list.clone()) {
    list.remove(object);
}
Xlsx
quelle
2

Eine alternative Java 8-Lösung mit Stream:

        theList = theList.stream()
            .filter(element -> !shouldBeRemoved(element))
            .collect(Collectors.toList());

In Java 7 können Sie stattdessen Guava verwenden:

        theList = FluentIterable.from(theList)
            .filter(new Predicate<String>() {
                @Override
                public boolean apply(String element) {
                    return !shouldBeRemoved(element);
                }
            })
            .toImmutableList();

Beachten Sie, dass das Guava-Beispiel zu einer unveränderlichen Liste führt, die möglicherweise Ihren Wünschen entspricht oder nicht.

Zsolt Sky
quelle
1

Sie können auch CopyOnWriteArrayList anstelle einer ArrayList verwenden. Dies ist der neueste empfohlene Ansatz ab JDK 1.5.

Pathikreet
quelle
1

In meinem Fall funktioniert die akzeptierte Antwort nicht. Sie stoppt die Ausnahme, verursacht jedoch einige Inkonsistenzen in meiner Liste. Die folgende Lösung funktioniert perfekt für mich.

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

for (String value: list) {
   if (value.length() > 5) { // your condition
       itemsToRemove.add(value);
   }
}
list.removeAll(itemsToRemove);

In diesem Code habe ich die zu entfernenden Elemente in einer anderen Liste hinzugefügt und dann die list.removeAllMethode verwendet, um alle erforderlichen Elemente zu entfernen.

Asad Ali Choudhry
quelle
0

"Soll ich zuerst die Liste klonen?"

Dies ist die einfachste Lösung. Entfernen Sie sie aus dem Klon und kopieren Sie sie nach dem Entfernen zurück.

Ein Beispiel aus meinem Rummikub-Spiel:

SuppressWarnings("unchecked")
public void removeStones() {
  ArrayList<Stone> clone = (ArrayList<Stone>) stones.clone();
  // remove the stones moved to the table
  for (Stone stone : stones) {
      if (stone.isOnTable()) {
         clone.remove(stone);
      }
  }
  stones = (ArrayList<Stone>) clone.clone();
  sortStones();
}
Arjen Rodenhuis
quelle
2
Downvoter sollten vor dem Downvoting mindestens einen Kommentar hinterlassen.
OneWorld
2
An dieser Antwort ist an sich nichts auszusetzen. Erwarten Sie vielleicht, dass dies stones = (...) clone.clone();überflüssig ist. Würde nicht stones = clone;das gleiche tun?
Wikingersteve
Ich stimme zu, das zweite Klonen ist nicht erforderlich. Sie können dies weiter vereinfachen, indem Sie den Klon durchlaufen und Elemente direkt aus entfernen stones. Auf diese Weise brauchen Sie nicht einmal die cloneVariable: for (Stone stone : (ArrayList<Stone>) stones.clone()) {...
Zsolt Sky
0

Wenn Sie alle Elemente aus der Liste entfernen möchten, können Sie jedes Element durchlaufen und dann Folgendes aufrufen:

list.clear()
Gibolt
quelle
0

Ich komme spät an, ich weiß, aber ich beantworte dies, weil ich denke, dass diese Lösung einfach und elegant ist:

List<String> listFixed = new ArrayList<String>();
List<String> dynamicList = new ArrayList<String>();

public void fillingList() {
    listFixed.add("Andrea");
    listFixed.add("Susana");
    listFixed.add("Oscar");
    listFixed.add("Valeria");
    listFixed.add("Kathy");
    listFixed.add("Laura");
    listFixed.add("Ana");
    listFixed.add("Becker");
    listFixed.add("Abraham");
    dynamicList.addAll(listFixed);
}

public void updatingListFixed() {
    for (String newList : dynamicList) {
        if (!listFixed.contains(newList)) {
            listFixed.add(newList);
        }
    }

    //this is for add elements if you want eraser also 

    String removeRegister="";
    for (String fixedList : listFixed) {
        if (!dynamicList.contains(fixedList)) {
            removeResgister = fixedList;
        }
    }
    fixedList.remove(removeRegister);
}

All dies dient zum Aktualisieren von einer Liste in eine andere, und Sie können alles aus nur einer Liste erstellen. Bei der Methodenaktualisierung überprüfen Sie beide Listen und können Elemente zwischen Listen löschen oder hinzufügen. Dies bedeutet, dass beide Listen immer die gleiche Größe haben

Erobere Arguello Flores
quelle
0

Verwenden Sie Iterator anstelle von Array List

Lassen Sie eine Menge in einen Iterator mit Typübereinstimmung konvertieren

Gehen Sie zum nächsten Element und entfernen Sie es

Iterator<Insured> itr = insuredSet.iterator();
while (itr.hasNext()) { 
    itr.next();
    itr.remove();
}

Hier ist es wichtig, zum nächsten zu wechseln, da der Index zum Entfernen des Elements erforderlich ist.

user8009263
quelle
0

Was ist mit

import java.util.Collections;

List<A> abc = Collections.synchronizedList(new ArrayList<>());
joseluisbz
quelle
-3

Fügen Sie einfach eine Pause nach Ihrer ArrayList.remove (A) -Anweisung hinzu

Sebastian Altamirano
quelle
Könnten Sie bitte eine Erklärung hinzufügen?
xskxzr