Übersicht
In diesem kurzen Tutorial werden wir über vier verschiedene Möglichkeiten sprechen, Elemente aus Java-Sammlungen zu entfernen, die bestimmten Prädikaten entsprechen.
Wir werden uns natürlich auch mit einigen Vorbehalten befassen.
Definieren unserer Sammlung
Zunächst werden wir zwei Ansätze erläutern, bei denen die ursprüngliche Datenstruktur verändert wird. Dann werden wir über zwei andere Optionen sprechen, die, anstatt die Elemente zu entfernen, eine Kopie der ursprünglichen Sammlung ohne sie erstellen.
Lassen Sie uns die folgende Sammlung in unseren Beispielen verwenden, um zu demonstrieren, wie wir das gleiche Ergebnis mit verschiedenen Methoden erreichen können:
Collection<String> names = new ArrayList<>();names.add("John");names.add("Ana");names.add("Mary");names.add("Anthony");names.add("Mark");
Elemente mit Iterator entfernen
Java’s Iterator ermöglicht es uns, jedes einzelne Element innerhalb einer Sammlung zu gehen und zu entfernen.
Um dies zu tun, müssen wir zunächst mit der Iterator-Methode einen Iterator über die Elemente abrufen. Danach können wir jedes Element mit Hilfe von next besuchen und mit remove entfernen:
Iterator<String> i = names.iterator();while(i.hasNext()) { String e = i.next(); if (e.startsWith("A")) { i.remove(); }}
Trotz der Einfachheit gibt es einige Vorbehalte, die wir beachten sollten:
- Abhängig von der Collection können wir auf ConcurrentModificationException-Ausnahmen stoßen
- Wir müssen über die Elemente iterieren, bevor wir sie entfernen können
- Abhängig von der Collection kann sich remove anders verhalten als erwartet. Z.B.: ArrayList.Iterator entfernt das Element aus der Sammlung und verschiebt die nachfolgenden Daten nach links, während LinkedList.Iterator einfach den Zeiger auf das nächste Element anpasst. Daher schneidet LinkedList.Iterator beim Entfernen von Elementen wesentlich besser ab als ArrayList.Iterator
Java 8 und Collection.removeIf()
Java 8 führte eine neue Methode in die Collection-Schnittstelle ein, die eine prägnantere Methode zum Entfernen von Elementen mithilfe von Predicate bietet:
names.removeIf(e -> e.startsWith("A"));
Es ist wichtig anzumerken, dass removeIf im Gegensatz zum Iterator-Ansatz sowohl bei LinkedList als auch bei ArrayList ähnlich gut funktioniert.
In Java 8 setzt ArrayList die Standardimplementierung außer Kraft – die sich auf Iterator stützt – und implementiert eine andere Strategie: Zuerst durchläuft es die Elemente und markiert diejenigen, die mit unserem Prädikat übereinstimmen; danach durchläuft es ein zweites Mal, um die Elemente zu entfernen (und zu verschieben), die in der ersten Iteration markiert wurden.
Java 8 und die Einführung von Stream
Eine der wichtigsten neuen Funktionen in Java 8 war die Einführung von Stream (und Collectors). Es gibt viele Möglichkeiten, einen Stream aus einer Quelle zu erzeugen. Die meisten Operationen, die sich auf die Stream-Instanz auswirken, verändern jedoch nicht die Quelle, sondern die API konzentriert sich auf die Erstellung von Kopien einer Quelle und die Durchführung aller Operationen, die wir in ihnen benötigen.
Lassen Sie uns einen Blick darauf werfen, wie wir Stream und Collectors verwenden können, um Elemente zu finden/zu filtern, die mit unserem Prädikat übereinstimmen bzw. nicht übereinstimmen.
5.1. Entfernen von Elementen mit Stream
Entfernen, oder besser gesagt, Filtern von Elementen mit Stream ist recht einfach, wir müssen nur eine Instanz von Stream mit unserer Collection erstellen, den Filter mit unserem Prädikat aufrufen und dann das Ergebnis mit Hilfe von Collectors sammeln:
Collection<String> filteredCollection = names .stream() .filter(e -> !e.startsWith("A")) .collect(Collectors.toList());
Streaming ist weniger invasiv als die vorherigen Ansätze, es fördert die Isolierung und ermöglicht die Erstellung mehrerer Kopien aus derselben Quelle. Wir sollten jedoch bedenken, dass es auch den von unserer Anwendung verwendeten Speicher erhöht.
5.2. Collectors.partitioningBy
Die Kombination von Stream.filter und Collectors ist recht praktisch, auch wenn wir möglicherweise auf Szenarien stoßen, in denen wir sowohl übereinstimmende als auch nicht übereinstimmende Elemente benötigen. In solchen Fällen können wir die Vorteile von Collectors.partitioningBy nutzen:
Map<Boolean, List<String>> classifiedElements = names .stream() .collect(Collectors.partitioningBy((String e) -> !e.startsWith("A")));String matching = String.join(",", classifiedElements.get(true));String nonMatching = String.join(",", classifiedElements.get(false));
Diese Methode gibt eine Map zurück, die nur zwei Schlüssel enthält, true und false, die jeweils auf eine Liste verweisen, die die übereinstimmenden bzw. nicht übereinstimmenden Elemente enthält.
Abschluss
In diesem Artikel haben wir uns einige Methoden zum Entfernen von Elementen aus Collections und einige ihrer Vorbehalte angesehen.