Frage Iterieren durch eine Sammlung, Vermeiden von ConcurrentModificationException beim Entfernen einer Schleife


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

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

ConcurrentModificationException etc ... das funktioniert manchmal, aber nicht immer. Hier ist ein spezifischer Code:

public static void main(String[] args) {
    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));
    }

    for (Integer i : l) {
        if (i.intValue() == 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 Objekt in einer Schleife aus der Sammlung entfernen, ohne diese Ausnahme auszulösen?

Ich benutze auch ein Willkürliches Collection hier, nicht unbedingt ein ArrayList, auf die man sich nicht verlassen kann get.


1028
2017-10-21 23:23


Ursprung


Antworten:


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

Quelle: docs.oracle> Die Sammlungsschnittstelle


Und in ähnlicher Weise, wenn Sie eine haben ListIterator und wollen hinzufügen Artikel, die Sie verwenden können ListIterator#add, aus dem gleichen Grund, den Sie verwenden können Iterator#remove- Es wurde entwickelt, um es zu erlauben.


1466
2017-10-21 23:27



Das funktioniert:

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

Ich nahm an, dass, da eine foreach-Schleife syntaktischer Zucker zum Iterieren ist, die Verwendung eines Iterators nicht helfen würde ... aber das gibt Ihnen das .remove() Funktionalität.


320
2017-10-21 23:26



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

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

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

156
2018-05-28 10:11



Da die Frage bereits beantwortet wurde, d. H. Der beste Weg besteht darin, die remove-Methode des Iterator-Objekts zu verwenden, würde ich auf die Besonderheiten der Stelle eingehen, an der der Fehler aufgetreten ist "java.util.ConcurrentModificationException" ist geworfen.

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

Der Code für den nächsten 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 als implementiert

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

So, wie Sie sehen können, wenn Sie explizit versuchen, ein Element aus der Sammlung zu entfernen. Es fuehrt zu modCount anders werden expectedModCount, was zu der Ausnahme führt ConcurrentModificationException.


38
2018-05-15 19:57



Sie können den Iterator entweder direkt wie oben erwähnt verwenden, oder Sie können eine zweite Sammlung behalten und jedes Element, das Sie entfernen möchten, in die neue Sammlung einfügen und am Ende removeAll. Dies ermöglicht es Ihnen, die Typ-Sicherheit der for-each-Schleife auf Kosten von erhöhtem Speicherverbrauch und CPU-Zeit zu verwenden (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);
}

22
2017-10-21 23:32



In solchen Fällen ist ein üblicher Trick (war?) Rückwärts zu gehen:

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

Das heißt, ich bin mehr als glücklich, dass Sie bessere Möglichkeiten in Java 8, z. removeIf oder filter auf Bächen.


17
2017-08-29 09:56



Gleiche Antwort wie Claudius mit einer for-Schleife:

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

14
2017-08-21 12:39



Mit Eclipse-Sammlungen (früher GS Sammlungen), die Methode removeIf definiert am MutableCollection wird funktionieren:

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 Anruf zu Predicates.cast() ist hier notwendig, weil ein Standard ist removeIf Methode wurde auf der hinzugefügt java.util.Collection Schnittstelle in Java 8.

Hinweis: Ich bin ein Committer für Eclipse-Sammlungen.


11
2017-12-18 23:08



Erstellen Sie eine Kopie der vorhandenen Liste und durchlaufen Sie eine neue Kopie.

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

6
2018-06-26 05:28



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++;
      }

6
2018-04-16 20:29



Leute behaupten einen kippen Entfernen von einer Sammlung, die von einer foreach-Schleife iteriert wird. Ich wollte nur darauf hinweisen, dass das ist technisch falsch und genau beschreiben (ich weiß, die Frage des OP ist so weit fortgeschritten, um dies zu vermeiden) der Code hinter dieser Annahme:

    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 das Iterierte nicht entfernen können Colletion Sie können die Iteration dann nicht fortsetzen, wenn Sie dies getan haben. Daher die break im obigen Code.

Entschuldigung, wenn diese Antwort ein etwas spezialisierterer Anwendungsfall ist und besser zum Original passt Faden Ich bin hier angekommen, der ist als Duplikat markiert (trotz dieses mehr nuancierten Threads) und ist gesperrt.


3
2018-03-17 11:02