webentwicklung-frage-antwort-db.com.de

Entfernen Sie während des Iterierens Elemente aus der Sammlung

AFAIK gibt es zwei Ansätze:

  1. Iteriere über eine Kopie der Sammlung
  2. Verwenden Sie den Iterator der aktuellen Sammlung

Zum Beispiel,

List<Foo> fooListCopy = new ArrayList<Foo>(fooList);
for(Foo foo : fooListCopy){
    // modify actual fooList
}

und

Iterator<Foo> itr = fooList.iterator();
while(itr.hasNext()){
    // modify actual fooList using itr.remove()
}

Gibt es Gründe, einen Ansatz dem anderen vorzuziehen (z. B. den ersten Ansatz aus einfachen Gründen der Lesbarkeit vorziehen)?

150
user1329572

Lassen Sie mich einige Beispiele mit Alternativen geben, um eine ConcurrentModificationException zu vermeiden.

Angenommen, wir haben die folgende Büchersammlung

List<Book> books = new ArrayList<Book>();
books.add(new Book(new ISBN("0-201-63361-2")));
books.add(new Book(new ISBN("0-201-63361-3")));
books.add(new Book(new ISBN("0-201-63361-4")));

Sammeln und entfernen

Die erste Technik besteht darin, alle Objekte zu sammeln, die wir löschen möchten (z. B. mithilfe einer erweiterten for-Schleife), und nachdem wir die Iteration abgeschlossen haben, entfernen wir alle gefundenen Objekte.

ISBN isbn = new ISBN("0-201-63361-2");
List<Book> found = new ArrayList<Book>();
for(Book book : books){
    if(book.getIsbn().equals(isbn)){
        found.add(book);
    }
}
books.removeAll(found);

Dies setzt voraus, dass die gewünschte Operation "Löschen" ist. 

Wenn Sie diese Methode "hinzufügen" möchten, würde dies ebenfalls funktionieren, aber ich würde davon ausgehen, dass Sie eine andere Auflistung durchlaufen würden, um zu bestimmen, welche Elemente Sie einer zweiten Auflistung hinzufügen möchten, und dann am Ende eine addAll-Methode ausgeben.

Verwenden von ListIterator

Wenn Sie mit Listen arbeiten, besteht eine andere Methode in der Verwendung einer ListIterator, die das Entfernen und Hinzufügen von Elementen während der Iteration selbst unterstützt.

ListIterator<Book> iter = books.listIterator();
while(iter.hasNext()){
    if(iter.next().getIsbn().equals(isbn)){
        iter.remove();
    }
}

Ich habe wieder die "remove" -Methode in dem obigen Beispiel verwendet. Dies scheint Ihre Frage zu implizieren. Sie können jedoch auch ihre add-Methode verwenden, um während der Iteration neue Elemente hinzuzufügen.

Verwenden von JDK 8

Für diejenigen, die mit Java 8 oder höheren Versionen arbeiten, gibt es eine Reihe anderer Techniken, mit denen Sie diese nutzen können.

Sie können die neue removeIf-Methode in der Collection-Basisklasse verwenden:

ISBN other = new ISBN("0-201-63361-2");
books.removeIf(b -> b.getIsbn().equals(other));

Oder verwenden Sie die neue Stream-API:

ISBN other = new ISBN("0-201-63361-2");
List<Book> filtered = books.stream()
                           .filter(b -> b.getIsbn().equals(other))
                           .collect(Collectors.toList());

In diesem letzten Fall ordnen Sie zum Herausfiltern von Elementen aus einer Sammlung den ursprünglichen Verweis der gefilterten Sammlung (d. H. books = filtered) zu oder verwenden die gefilterte Sammlung zur removeAll der gefundenen Elemente aus der ursprünglichen Sammlung (d. H. books.removeAll(filtered)).

Sublist oder Subset verwenden  

Es gibt auch andere Alternativen. Wenn die Liste sortiert ist und Sie fortlaufende Elemente entfernen möchten, können Sie eine Unterliste erstellen und diese dann löschen:

books.subList(0,5).clear();

Da die Unterliste von der ursprünglichen Liste unterstützt wird, ist dies eine effiziente Methode zum Entfernen dieser Unterkollektion von Elementen.

Etwas Ähnliches konnte mit sortierten Sets mit der NavigableSet.subSet-Methode oder einer der dort angebotenen Slicing-Methoden erreicht werden.

Überlegungen:

Welche Methode Sie verwenden, hängt möglicherweise davon ab, was Sie beabsichtigen

  • Die collect- und removeAl-Technik funktioniert mit jeder Collection (Collection, List, Set usw.). 
  • Die ListIterator-Technik funktioniert offensichtlich nur mit Listen, vorausgesetzt, ihre vorgegebene ListIterator-Implementierung unterstützt das Hinzufügen und Entfernen von Operationen. 
  • Die Iterator-Methode würde mit jeder Art von Erfassung funktionieren, unterstützt jedoch nur Vorgänge zum Entfernen.
  • Bei dem Ansatz ListIterator/Iterator besteht der offensichtliche Vorteil darin, nichts kopieren zu müssen, da wir bei der Iteration entfernen. Das ist also sehr effizient. 
  • Das Beispiel für die JDK 8-Streams entfernte eigentlich nichts, sondern suchte nach den gewünschten Elementen. Dann ersetzten wir die ursprüngliche Referenz der Sammlung durch die neue Referenz und ließen die alte Müllsammlung sammeln. Wir durchlaufen also nur einmal die Sammlung und das wäre effizient.
  • Der Nachteil von collect und removeAll besteht darin, dass wir zweimal iterieren müssen. Zuerst iterieren wir in der Fußschleife nach einem Objekt, das unseren Entfernungskriterien entspricht, und sobald wir es gefunden haben, bitten wir, es aus der ursprünglichen Sammlung zu entfernen. Dies würde eine zweite Iterationsarbeit erfordern, um nach diesem Objekt zu suchen entfernen Sie es. 
  • Ich denke, es ist erwähnenswert, dass die remove-Methode der Iterator-Schnittstelle in Javadocs als "optional" markiert ist. Dies bedeutet, dass es Iterator-Implementierungen gibt, die UnsupportedOperationException auslösen, wenn wir die remove-Methode aufrufen. Ich würde sagen, dass dieser Ansatz weniger sicher ist als andere, wenn wir die Unterstützung des Iterators beim Entfernen von Elementen nicht garantieren können.
290
Edwin Dalorzo

Gibt es Gründe, einen Ansatz dem anderen vorzuziehen?

Der erste Ansatz wird funktionieren, hat aber den offensichtlichen Aufwand beim Kopieren der Liste.

Der zweite Ansatz funktioniert nicht, da viele Container während der Iteration keine Änderungen zulassen. Dies beinhaltet ArrayList .

Wenn nur das aktuelle Element entfernt werden soll, können Sie den zweiten Ansatz mithilfe von itr.remove() ausführen (dh die Methode Iterators remove() verwenden, nicht die Methode container ). Dies wäre meine bevorzugte Methode für Iteratoren, die remove() unterstützen.

11
NPE

In Java 8 gibt es einen anderen Ansatz. Sammlung # removeIf

z.B:

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

list.removeIf(i -> i > 2);
10
Santhosh

Nur der zweite Ansatz wird funktionieren. Sie können die Sammlung während der Iteration nur mit iterator.remove() ändern. Alle anderen Versuche führen zu ConcurrentModificationException.

4
AlexR

Sie können das zweite nicht tun, denn selbst wenn Sie die remove()-Methode für Iterator , verwenden, wird eine Ausnahme ausgelöst

Persönlich würde ich die erste Instanz für alle Collection-Instanzen vorziehen, obwohl das Erstellen der neuen Collection-Datei zusätzlich mitgehört hat. Ich finde es weniger fehleranfällig während der Bearbeitung durch andere Entwickler. Bei einigen Collection-Implementierungen wird der Iterator remove() unterstützt, bei anderen nicht. Weitere Informationen finden Sie in den Dokumenten für Iterator

Die dritte Alternative besteht darin, ein neues Collection zu erstellen, über das Original zu iterieren und alle Mitglieder des ersten Collection dem zweiten Collection hinzuzufügen, die nicht zum Löschen sind. Abhängig von der Größe des Collection und der Anzahl der Löschungen kann dies im Vergleich zum ersten Ansatz erheblich Speicherplatz sparen. 

1
Jon

Ich würde die zweite wählen, da Sie keine Kopie des Speichers machen müssen und der Iterator schneller arbeitet. So sparen Sie Speicher und Zeit.

0
Calin Andrei

Es gibt auch eine einfache Lösung, um eine Collection zu "iterieren" und jedes Element zu entfernen.

List<String> list = new ArrayList<>();
//Fill the list

Es wird einfach eine Schleife beim Schleifen erstellt, bis die Liste leer ist, und bei jeder Iteration entfernen wir das erste Element mit remove(0).

while(!list.isEmpty()){
    String s = list.remove(0);
    // do you thing
}

Ich glaube nicht, dass dies im Vergleich zu Iterator eine Verbesserung darstellt, es musste noch eine veränderliche Liste erstellt werden, aber ich mag die Einfachheit dieser Lösung.

0
AxelH