674 Stimmen

Aufruf von remove in der foreach-Schleife in Java

Ist es in Java legal, remove für eine Sammlung aufzurufen, wenn man die Sammlung mit einer foreach-Schleife durchläuft? Zum Beispiel:

List<String> names = ....
for (String name : names) {
   // Do something
   names.remove(name).
}

Ist es außerdem legal, Elemente zu entfernen, die noch nicht überarbeitet wurden? Zum Beispiel,

//Assume that the names list as duplicate entries
List<String> names = ....
for (String name : names) {
    // Do something
    while (names.remove(name));
}

34 Stimmen

Kein guter Plan. Nur weil die Sprache es bei einem bestimmten Lauf toleriert, ist es noch lange keine gute Idee.

0 Stimmen

Sie müssen mich in einer schlechten Stimmung erwischt haben, aber mir scheint, die Antwort auf diese Frage kommt direkt aus der foreach-Dokumentation.

2 Stimmen

1030voto

Mark Punkte 28072

Um sicher aus einer Sammlung zu entfernen, während Sie darüber iterieren, sollten Sie einen Iterator verwenden.

Zum Beispiel:

List<String> names = ....
Iterator<String> i = names.iterator();
while (i.hasNext()) {
   String s = i.next(); // must be called before you can call i.remove()
   // Do something
   i.remove();
}

Desde el Java-Dokumentation :

Die Iteratoren, die von den Iteratoren und ListIteratoren dieser Klasse zurückgegeben werden Methoden zurückgegebenen Iteratoren sind ausfallsicher: Wenn die Liste zu irgendeinem Zeitpunkt nach der Liste strukturell verändert wird, nachdem der Iterator erstellt wurde, und zwar auf irgendeine Weise außer durch die Iterator eigenen remove oder add Methoden, wird der Iterator eine ConcurrentModificationException. Angesichts der gleichzeitigen Änderung scheitert der Iterator schnell und sauber, anstatt Risiko eines willkürlichen, nicht-deterministischen Verhaltens zu einem unbestimmten Zeitpunkt in der Zukunft.

Was vielen Anfängern vielleicht nicht klar ist, ist die Tatsache, dass die Iteration über eine Liste mit Hilfe der for/foreach-Konstrukte implizit einen Iterator erzeugt, der zwangsläufig unzugänglich ist. Diese Information finden Sie unter aquí

180voto

Jared Oberhaus Punkte 14325

Das wollen Sie nicht tun. Es kann je nach Sammlung zu undefiniertem Verhalten führen. Sie sollten eine Iterator direkt. Obwohl das for each-Konstrukt ein syntaktischer Zucker ist und in Wirklichkeit einen Iterator verwendet, wird er vor Ihrem Code verborgen, so dass Sie nicht auf ihn zugreifen können, um die [Iterator.remove](http://java.sun.com/javase/6/docs/api/java/util/Iterator.html#remove()) .

Das Verhalten eines Iterators ist nicht spezifiziert, wenn die zugrunde liegende Sammlung geändert wird, während die Iteration in irgendeiner Weise verändert wird als durch den Aufruf dieser Methode.

Schreiben Sie stattdessen Ihren Code:

List<String> names = ....
Iterator<String> it = names.iterator();
while (it.hasNext()) {

    String name = it.next();
    // Do something
    it.remove();
}

Beachten Sie, dass der Code Iterator.remove pas List.remove .

Nachtrag:

Selbst wenn Sie ein Element entfernen, über das noch nicht iteriert wurde, sollten Sie die Sammlung nicht ändern und dann die Iterator . Es könnte die Sammlung in einer Weise verändern, die überraschend ist und sich auf zukünftige Vorgänge in der Iterator .

96voto

ktamlyn Punkte 4254
for (String name : new ArrayList<String>(names)) {
    // Do something
    names.remove(nameToRemove);
}

Sie klonen die Liste names und iterieren Sie durch den Klon, während Sie aus der ursprünglichen Liste entfernen. Ein bisschen sauberer als die obere Antwort.

74voto

Yishai Punkte 87548

Der Java-Entwurf der "erweiterten for-Schleife" sah vor, den Iterator nicht für den Code freizugeben, aber die einzige Möglichkeit, ein Element sicher zu entfernen, ist der Zugriff auf den Iterator. In diesem Fall müssen Sie es also auf die alte Weise machen:

 for(Iterator<String> i = names.iterator(); i.hasNext();) {
       String name = i.next();
       //Do Something
       i.remove();
 }

Wenn sich die erweiterte for-Schleife im echten Code wirklich lohnt, könnten Sie die Elemente zu einer temporären Sammlung hinzufügen und removeAll für die Liste nach der Schleife aufrufen.

EDIT (re addendum): Nein, die Liste in irgendeiner Weise außerhalb der iterator.remove()-Methode während der Iteration zu ändern wird Probleme verursachen. Die einzige Möglichkeit, dies zu umgehen, ist die Verwendung einer CopyOnWriteArrayList, aber das ist wirklich für Gleichzeitigkeitsprobleme gedacht.

Der billigste Weg (in Bezug auf die Anzahl der Codezeilen), um Duplikate zu entfernen, besteht darin, die Liste in ein LinkedHashSet (und dann wieder in eine Liste, falls erforderlich) zu übertragen. Dadurch wird die Einfügereihenfolge beibehalten, während Duplikate entfernt werden.

35voto

Serafeim Punkte 14409

Ich wusste nicht, über Iteratoren, aber hier ist, was ich tat, bis heute, um Elemente aus einer Liste innerhalb einer Schleife zu entfernen:

List<String> names = .... 
for (i=names.size()-1;i>=0;i--) {    
    // Do something    
    names.remove(i);
} 

Dies funktioniert immer und kann auch in anderen Sprachen oder Strukturen verwendet werden, die keine Iteratoren unterstützen.

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