it-swarm.com.de

Durchlaufen einer Auflistung, wobei ConcurrentModificationException beim Entfernen von Objekten in einer Schleife vermieden wird

Wir alle wissen, dass Sie das nicht können:

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

ConcurrentModificationException etc ... das funktioniert anscheinend 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);
}

Dies führt natürlich zu:

Exception in thread "main" Java.util.ConcurrentModificationException

... obwohl mehrere Threads es nicht tun ... Wie auch immer.

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

Ich verwende hier auch ein willkürliches Collection, nicht unbedingt ein ArrayList, sodass Sie sich nicht auf get verlassen können.

1140
Claudiu

Iterator.remove() ist sicher, du kannst es so benutzen:

_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() die einzig sichere Möglichkeit ist, eine Sammlung während der Iteration zu ändern. Das Verhalten ist nicht angegeben, wenn die zugrunde liegende Auflistung während der Iteration auf andere Weise geändert wird .

Quelle: docs.Oracle> The Collection Interface


Und in ähnlicher Weise können Sie ListIterator#add verwenden, wenn Sie ein ListIterator haben und hinzufügen möchten. Aus demselben Grund können Sie _Iterator#remove_ - es wurde entwickelt, um es zu ermöglichen.


In Ihrem Fall haben Sie versucht, aus einer Liste zu entfernen, aber die gleiche Einschränkung gilt, wenn Sie versuchen, put in Map zu ändern, während Sie den Inhalt durchlaufen.

1553
Bill K

Das funktioniert:

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

Ich bin davon ausgegangen, dass die Verwendung eines Iterators nicht hilfreich ist, da eine foreach-Schleife syntaktischer Zucker zum Iterieren ist ... aber es gibt Ihnen diese .remove() -Funktionalität.

334
Claudiu

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

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

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

Da die Frage bereits beantwortet wurde, d. H. Der beste Weg ist, die Entfernungsmethode des Iteratorobjekts zu verwenden, würde ich auf die Einzelheiten der Stelle eingehen, an der der Fehler "Java.util.ConcurrentModificationException" ausgegeben wird.

Jede Auflistungsklasse verfügt über eine private Klasse, die die Iterator-Schnittstelle implementiert und Methoden wie next(), remove() und hasNext() bereitstellt.

Der Code für next sieht ungefähr so ​​aus ...

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

Hier wird die Methode checkForComodification implementiert als

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

Sie sehen also, wenn Sie explizit versuchen, ein Element aus der Auflistung zu entfernen. Dies führt dazu, dass modCount sich von expectedModCount unterscheidet, was zur Ausnahme ConcurrentModificationException führt.

40
Ashish

Sie können entweder den Iterator direkt wie erwähnt verwenden oder eine zweite Sammlung führen und jedes Element, das Sie entfernen möchten, zur neuen Sammlung hinzufügen und dann am Ende alle Elemente entfernen. Auf diese Weise können Sie die Typensicherheit der for-each-Schleife auf Kosten des erhöhten Speicherverbrauchs und der CPU-Zeit weiter nutzen (sollte kein großes Problem sein, es sei denn, Sie haben wirklich große Listen oder einen wirklich alten Computer).

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

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

In solchen Fällen ist (war?) Ein häufiger Trick, rückwärts zu gehen:

for(int i = l.size() - 1; i >= 0; i --) {
  if (l.get(i) == 5) {
    l.remove(i);
  }
}

Trotzdem bin ich mehr als glücklich, dass Sie in Java 8 bessere Möglichkeiten haben, z. removeIf oder filter in Streams.

17
Landei

Gleiche Antwort wie Claudius mit einer for-Schleife:

for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
    Object object = it.next();
    if (test) {
        it.remove();
    }
}
16
Antzi

Mit Eclipse Collections (früher GS Collections ) funktioniert die Methode removeIf, die am MutableCollection definiert wurde:

MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.lessThan(3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);

Mit Java 8 Lambda-Syntax kann dies wie folgt geschrieben werden:

MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.cast(integer -> integer < 3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);

Der Aufruf von Predicates.cast() ist hier erforderlich, da in Java 8 eine Standardmethode removeIf für die Schnittstelle Java.util.Collection hinzugefügt wurde.

Hinweis: Ich bin ein Committer für Eclipse Collections .

11
Donald Raab

Erstellen Sie eine Kopie der vorhandenen Liste und iterieren Sie über eine neue Kopie.

for (String str : new ArrayList<String>(listOfStr))     
{
    listOfStr.remove(/* object reference or index */);
}
9
Priyank Doshi

Mit einer traditionellen for-Schleife

ArrayList<String> myArray = new ArrayList<>();

   for (int i = 0; i < myArray.size(); ) {
        String text = myArray.get(i);
        if (someCondition(text))
             myArray.remove(i);
        else 
             i++;
      }
7
Lluis Felisart

Leute behaupten, man könne nicht aus einer Sammlung entfernen, die von einer foreach-Schleife durchlaufen wird. Ich wollte nur darauf hinweisen, dass technisch inkorrekt ist, und genau beschreiben (ich weiß, dass die Frage des OP so weit fortgeschritten ist, dass ich das nicht weiß), welcher Code hinter dieser Annahme steckt:

    for (TouchableObj obj : untouchedSet) {  // <--- This is where ConcurrentModificationException strikes
        if (obj.isTouched()) {
            untouchedSet.remove(obj);
            touchedSt.add(obj);
            break;  // this is key to avoiding returning to the foreach
        }
    }

Es ist nicht so, dass Sie nicht aus dem iterierten Colletion entfernen können, sondern dass Sie die Iteration nicht fortsetzen können, wenn Sie dies tun. Daher das break im obigen Code.

Entschuldigung, wenn diese Antwort ein etwas speziellerer Anwendungsfall ist und besser zum Original passt Thread Ich bin hierher gekommen, dass einer als Duplikat (obwohl dieser Thread nuancierter erscheint) von diesem markiert und gesperrt ist.

7
John

Mit ListIterator können Sie Elemente zur Liste hinzufügen oder daraus entfernen. Angenommen, Sie haben eine Liste von Car Objekten:

List<Car> cars = ArrayList<>();
// add cars here...

for (ListIterator<Car> carIterator = cars.listIterator();  carIterator.hasNext(); )
{
   if (<some-condition>)
   { 
      carIterator().remove()
   }
   else if (<some-other-condition>)
   { 
      carIterator().add(aNewCar);
   }
}
2
james.garriss

Eine andere Möglichkeit besteht darin, eine Kopie Ihrer ArrayList zu erstellen:

List<Object> l = ...

List<Object> iterationList = ImmutableList.copyOf(l);

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

}

1
Nestor Milyaev

Der beste Weg (empfohlen) ist die Verwendung des Java.util.Concurrent-Pakets. Durch die Verwendung dieses Pakets können Sie diese Ausnahme auf einfache Weise vermeiden. Siehe Modifizierter Code

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

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

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

        System.out.println(l);
    }
1
jagdish khetre

Ich habe einen Vorschlag für das obige Problem. Keine sekundäre Liste oder zusätzliche Zeit erforderlich. Finden Sie ein Beispiel, das dasselbe, aber auf andere Weise ausführt.

//"list" is ArrayList<Object>
//"state" is some boolean variable, which when set to true, Object will be removed from the list
int index = 0;
while(index < list.size()) {
    Object r = list.get(index);
    if( state ) {
        list.remove(index);
        index = 0;
        continue;
    }
    index += 1;
}

Dies würde die Nebenläufigkeitsausnahme vermeiden.

1

ConcurrentHashMap oder ConcurrentLinkedQueue oder ConcurrentSkipListMap sind möglicherweise eine weitere Option, da sie niemals eine ConcurrentModificationException auslösen, selbst wenn Sie Elemente entfernen oder hinzufügen.

1
Yessy

Ich weiß, dass diese Frage nur von einem Collection ausgeht und nicht von einem List. Aber für diejenigen, die diese Frage lesen und tatsächlich mit einer List -Referenz arbeiten, können Sie ConcurrentModificationException mit einer while- -Schleife vermeiden (während Sie darin Änderungen vornehmen) wenn Sie Iterator vermeiden möchten (entweder wenn Sie es im Allgemeinen vermeiden möchten oder wenn Sie es im Besonderen vermeiden möchten, um eine andere Schleifenreihenfolge als das durchgehende Anhalten an jedem Element zu erreichen [was ich glaube] der einzige Befehl, den Iterator selbst ausführen kann]):

* Update: Siehe unten stehende Kommentare, die verdeutlichen, dass das Analoge auch mit der traditionellen - for-Schleife erreichbar ist.

final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
    list.add(i);
}

int i = 1;
while(i < list.size()){
    if(list.get(i) % 2 == 0){
        list.remove(i++);

    } else {
        i += 2;
    }
}

Keine ConcurrentModificationException von diesem Code.

Dort sehen wir, dass die Schleife nicht am Anfang beginnt und nicht bei jedem Element endet (was meines Erachtens Iterator selbst nicht kann). .

FWIW sehen wir auch, dass get für list aufgerufen wird, was nicht möglich wäre, wenn sein Verweis nur Collection wäre (anstelle des spezifischeren List- Typs) der Schnittstelle Collection) - List enthält get, die Schnittstelle Collection jedoch nicht. Ohne diesen Unterschied könnte die Referenz list stattdessen eine Collection [sein, und daher wäre diese Antwort technisch eine direkte Antwort anstelle einer tangentialen Antwort].

FWIWW-Code funktioniert nach der Änderung immer noch, sodass er bei jedem Element am Anfang und am Ende beginnt (genau wie bei Iterator order):

final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
    list.add(i);
}

int i = 0;
while(i < list.size()){
    if(list.get(i) % 2 == 0){
        list.remove(i);

    } else {
        ++i;
    }
}
0
cellepo

Eine Lösung könnte darin bestehen, die Liste zu drehen und das erste Element zu entfernen, um die ConcurrentModificationException oder die IndexOutOfBoundsException zu vermeiden

int n = list.size();
for(int j=0;j<n;j++){
    //you can also put a condition before remove
    list.remove(0);
    Collections.rotate(list, 1);
}
Collections.rotate(list, -1);
0
Rahul Vala
for (Integer i : l)
{
    if (i.intValue() == 5){
            itemsToRemove.add(i);
            break;
    }
}

Der Haken ist das nach dem Entfernen des Elements aus der Liste, wenn Sie den internen iterator.next () -Aufruf überspringen. es funktioniert noch! Obwohl ich nicht vorschlage, Code wie diesen zu schreiben, hilft es, das Konzept dahinter zu verstehen :-)

Prost!

0

Beispiel für eine Änderung der thread-sicheren Sammlung:

public class Example {
    private final List<String> queue = Collections.synchronizedList(new ArrayList<String>());

    public void removeFromQueue() {
        synchronized (queue) {
            Iterator<String> iterator = queue.iterator();
            String string = iterator.next();
            if (string.isEmpty()) {
                iterator.remove();
            }
        }
    }
}
0
Yazon2006

Ich weiß, dass diese Frage zu alt für Java 8 ist, aber für diejenigen, die Java 8 verwenden, können Sie einfach removeIf () verwenden:

Collection<Integer> l = new ArrayList<Integer>();

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

l.removeIf(i -> i.intValue() == 5);
0
pedram bashiri

Falls ArrayList: remove (int index) - falls (index die Position des letzten Elements ist), wird dies ohne System.arraycopy() vermieden und es wird dafür keine Zeit benötigt.

die ArrayCopy-Zeit nimmt zu, wenn (der Index abnimmt). Übrigens nehmen auch die Elemente der Liste ab!

die beste Methode zum Entfernen besteht darin, die Elemente in absteigender Reihenfolge zu entfernen: while(list.size()>0)list.remove(list.size()-1); // nimmt O(1) while(list.size()>0)list.remove(0); // nimmt O (Fakultativ (n))

//region prepare data
ArrayList<Integer> ints = new ArrayList<Integer>();
ArrayList<Integer> toRemove = new ArrayList<Integer>();
Random rdm = new Random();
long millis;
for (int i = 0; i < 100000; i++) {
    Integer integer = rdm.nextInt();
    ints.add(integer);
}
ArrayList<Integer> intsForIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsDescIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsIterator = new ArrayList<Integer>(ints);
//endregion

// region for index
millis = System.currentTimeMillis();
for (int i = 0; i < intsForIndex.size(); i++) 
   if (intsForIndex.get(i) % 2 == 0) intsForIndex.remove(i--);
System.out.println(System.currentTimeMillis() - millis);
// endregion

// region for index desc
millis = System.currentTimeMillis();
for (int i = intsDescIndex.size() - 1; i >= 0; i--) 
   if (intsDescIndex.get(i) % 2 == 0) intsDescIndex.remove(i);
System.out.println(System.currentTimeMillis() - millis);
//endregion

// region iterator
millis = System.currentTimeMillis();
for (Iterator<Integer> iterator = intsIterator.iterator(); iterator.hasNext(); )
    if (iterator.next() % 2 == 0) iterator.remove();
System.out.println(System.currentTimeMillis() - millis);
//endregion
  • für Indexschleife: 1090 ms
  • für desc index: 519 ms --- das beste
  • für Iterator: 1043 ms
0
Nurlan