Wir haben die Methode „collect()“ bereits mehrmals verwendet, um die vom Stream zurückgegebenen Elemente in einer ArrayList zu kombinieren. Dies ist eine Reduzierungsoperation, die zum Konvertieren einer Sammlung in einen anderen Typ (normalerweise eine veränderbare Sammlung) nützlich ist. Die Funktion „collect()“ kann, wenn sie in Kombination mit einigen Methoden in der Toolklasse „Collectors“ verwendet wird, großen Komfort bieten, wie wir in diesem Abschnitt vorstellen werden.
Lassen Sie uns weiterhin die vorherige Personenliste als Beispiel verwenden, um zu sehen, was die Methode „collect()“ leisten kann. Angenommen, wir möchten alle Personen aus der ursprünglichen Liste finden, die älter als 20 Jahre sind. Hier ist die Version, die mithilfe von Mutability und der Methode forEach() implementiert wurde:
Kopieren Sie den Codecode wie folgt:
List<Person>olderThan20 = new ArrayList<>(); people.stream()
.filter(person -> person.getAge() > 20)
.forEach(person -> oldThan20.add(person)); System.out.println("Personen älter als 20: " + oldThan20);
Wir verwenden die Methode filter(), um alle Personen, die älter als 20 sind, aus der Liste herauszufiltern. Dann fügen wir in der forEach-Methode Elemente zu einer ArrayList hinzu, die zuvor initialisiert wurde. Schauen wir uns zunächst die Ausgabe dieses Codes an und rekonstruieren ihn später.
Kopieren Sie den Codecode wie folgt:
Personen älter als 20: [Sara – 21, Jane – 21, Greg – 35]
Die Ausgabe des Programms ist korrekt, es gibt jedoch immer noch ein kleines Problem. Erstens ist das Hinzufügen von Elementen zu einer Sammlung ein Vorgang auf niedriger Ebene – es ist zwingend und nicht deklarativ. Wenn wir diese Iteration in eine parallele Iteration umwandeln wollen, müssen wir Aspekte der Thread-Sicherheit berücksichtigen – Variabilität erschwert die Parallelisierung. Glücklicherweise lässt sich dieses Problem leicht mit der Methode „collect()“ lösen. Mal sehen, wie das gelingt.
Die Methode „collect()“ akzeptiert einen Stream und sammelt ihn in einem Ergebniscontainer. Dazu muss es drei Dinge wissen:
+ So erstellen Sie einen Ergebniscontainer (z. B. mit der Methode ArrayList::new) + So fügen Sie dem Container ein einzelnes Element hinzu (z. B. mit der Methode ArrayList::add) + So führen Sie eine Ergebnismenge mit einer anderen zusammen (zum Beispiel mit der Methode ArrayList: :addAll)
Das letzte Element ist für serielle Vorgänge nicht erforderlich; der Code ist so konzipiert, dass er sowohl serielle als auch parallele Vorgänge unterstützt.
Wir stellen diese Vorgänge der Methode „collect“ zur Verfügung und lassen sie den gefilterten Stream sammeln.
Kopieren Sie den Codecode wie folgt:
List<Person>olderThan20 =
people.stream()
.filter(person -> person.getAge() > 20)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
System.out.println("Personen älter als 20: " + oldThan20);
Das Ergebnis dieses Codes ist das gleiche wie zuvor, es gibt jedoch viele Vorteile, ihn auf diese Weise zu schreiben.
Erstens ist unsere Programmiermethode fokussierter und ausdrucksvoller und vermittelt klar den Zweck der Sammlung der Ergebnisse in einer ArrayList. Der erste Parameter von Collect() ist eine Fabrik oder ein Produzent, und der nachfolgende Parameter ist eine Operation zum Sammeln von Elementen.
Zweitens können wir diese Iteration problemlos parallel durchführen, da wir keine expliziten Änderungen am Code vornehmen. Wir lassen die zugrunde liegende Bibliothek die Änderungen übernehmen und sie kümmert sich um die Koordination und Thread-Sicherheitsprobleme, auch wenn die ArrayList selbst nicht threadsicher ist – gute Arbeit.
Wenn die Bedingungen dies zulassen, kann die Methode „collect()“ Elemente parallel zu verschiedenen Unterlisten hinzufügen und diese dann auf threadsichere Weise zu einer großen Liste zusammenführen (der letzte Parameter wird für den Zusammenführungsvorgang verwendet).
Wir haben gesehen, dass die Verwendung der Methode „collect()“ gegenüber dem manuellen Hinzufügen von Elementen zu einer Liste viele Vorteile bietet. Schauen wir uns eine überladene Version dieser Methode an – sie ist einfacher und bequemer – sie benötigt einen Collector als Parameter. Dieser Collector ist eine Schnittstelle, die Produzenten, Addierer und Combiner umfasst. In früheren Versionen wurden diese Operationen als unabhängige Parameter übergeben. Die Collectors-Toolklasse stellt eine toList-Methode bereit, die eine Collector-Implementierung generieren kann, um Elemente zu einer ArrayList hinzuzufügen. Lassen Sie uns den vorherigen Code ändern und die Methode „collect()“ verwenden.
Kopieren Sie den Codecode wie folgt:
List<Person>olderThan20 =
people.stream()
.filter(person -> person.getAge() > 20)
.collect(Collectors.toList());
System.out.println("Personen älter als 20: " + oldThan20);
Es wird eine prägnante Version der Methode „collect()“ der Werkzeugklasse „Collectors“ verwendet, die jedoch auf mehr als eine Weise verwendet werden kann. In der Toolklasse „Collectors“ gibt es mehrere unterschiedliche Methoden zum Durchführen unterschiedlicher Erfassungs- und Hinzufügungsvorgänge. Beispielsweise gibt es neben der toList()-Methode auch die toSet()-Methode, die zu einem Set hinzugefügt werden kann, die toMap()-Methode, die zum Sammeln in einem Schlüsselwertsatz verwendet werden kann, und die Join()-Methode, die in einen String gespleißt werden kann. Wir können auch Methoden wie „mapping()“, „collectionAndThen()“, „minBy()“, „maxBy()“ und „groupingBy()“ kombinieren.
Lassen Sie uns die Methode groupingBy() verwenden, um Personen nach Alter zu gruppieren.
Kopieren Sie den Codecode wie folgt:
Map<Integer, List<Person>> peopleByAge =
people.stream()
.collect(Collectors.groupingBy(Person::getAge));
System.out.println("Gruppiert nach Alter: " + peopleByAge);
Rufen Sie einfach die Methode „collect()“ auf, um die Gruppierung abzuschließen. groupingBy() akzeptiert einen Lambda-Ausdruck oder eine Methodenreferenz – dies wird als Klassifizierungsfunktion bezeichnet – und gibt den Wert eines bestimmten Attributs des Objekts zurück, das gruppiert werden muss. Entsprechend dem von unserer Funktion zurückgegebenen Wert werden die Elemente im aufrufenden Kontext in eine bestimmte Gruppe eingeordnet. Die Ergebnisse der Gruppierung sind in der Ausgabe zu sehen:
Kopieren Sie den Codecode wie folgt:
Gruppiert nach Alter: {35=[Greg – 35], 20=[John – 20], 21=[Sara – 21, Jane – 21]}
Die Personen wurden nach Alter gruppiert.
Im vorherigen Beispiel haben wir Personen nach Alter gruppiert. Eine Variante der groupingBy()-Methode kann nach mehreren Bedingungen gruppieren. Die einfache Methode „groupingBy()“ verwendet einen Klassifikator zum Sammeln von Elementen. Der allgemeine GroupingBy()-Kollektor kann für jede Gruppe einen Kollektor angeben. Mit anderen Worten: Elemente durchlaufen während des Erfassungsprozesses verschiedene Klassifikatoren und Sammlungen, wie wir weiter unten sehen werden.
Um mit dem obigen Beispiel fortzufahren, holen wir uns dieses Mal statt einer Gruppierung nach Alter einfach die Namen der Personen und sortieren sie nach ihrem Alter.
Kopieren Sie den Codecode wie folgt:
Map<Integer, List<String>> nameOfPeopleByAge =
people.stream()
.sammeln(
groupingBy(Person::getAge, programming(Person::getName, toList())));
System.out.println("Personen nach Alter gruppiert: " + nameOfPeopleByAge);
Diese Version von groupingBy() akzeptiert zwei Parameter: Der erste ist das Alter, das die Bedingung für die Gruppierung darstellt, und der zweite ist ein Kollektor, der das von der Funktion „mapping()“ zurückgegebene Ergebnis ist. Diese Methoden stammen alle aus der Collectors-Toolklasse und werden statisch in diesen Code importiert. Die Methode „mapping()“ akzeptiert zwei Parameter: Einer ist das für die Zuordnung verwendete Attribut und der andere ist der Ort, an dem die Objekte gesammelt werden sollen, z. B. Liste oder Menge. Schauen wir uns die Ausgabe des obigen Codes an:
Kopieren Sie den Codecode wie folgt:
Personen nach Alter gruppiert: {35=[Greg], 20=[John], 21=[Sara, Jane]}
Wie Sie sehen können, wurden die Namen der Personen nach Alter gruppiert.
Schauen wir uns noch einmal eine Kombinationsoperation an: Gruppieren Sie nach dem ersten Buchstaben des Namens und wählen Sie dann die älteste Person in jeder Gruppe aus.
Kopieren Sie den Codecode wie folgt:
Comparator<Person> byAge = Comparator.comparing(Person::getAge);
Map<Character, Optional<Person>> oldestPersonOfEachLetter =
people.stream()
.collect(groupingBy(person -> person.getName().charAt(0),
reduction(BinaryOperator.maxBy(byAge))));
System.out.println("Älteste Person jedes Buchstabens:");
System.out.println(oldestPersonOfEachLetter);
Wir haben die Namen zunächst alphabetisch sortiert. Um dies zu erreichen, übergeben wir einen Lambda-Ausdruck als ersten Parameter von groupingBy(). Dieser Lambda-Ausdruck wird verwendet, um den ersten Buchstaben des Namens zur Gruppierung zurückzugeben. Der zweite Parameter ist nicht mehr „mapping()“, sondern führt eine Reduzierungsoperation aus. Innerhalb jeder Gruppe wird die Methode maxBy() verwendet, um das älteste Element aus allen Elementen abzuleiten. Die Syntax sieht aufgrund der vielen Operationen, die sie kombiniert, etwas aufgebläht aus, aber das Ganze sieht so aus: Gruppieren Sie nach dem ersten Buchstaben des Namens und arbeiten Sie sich dann bis zum ältesten in der Gruppe vor. Betrachten Sie die Ausgabe dieses Codes, der die älteste Person in einer Gruppe von Namen auflistet, die mit einem bestimmten Buchstaben beginnen.
Kopieren Sie den Codecode wie folgt:
Älteste Person jedes Buchstabens:
{S=Optional[Sara - 21], G=Optional[Greg - 35], J=Optional[Jane - 21]}
Wir haben bereits die Leistungsfähigkeit der Methode „collect()“ und der Dienstprogrammklasse „Collectors“ kennengelernt. Nehmen Sie sich in der offiziellen Dokumentation Ihrer IDE oder Ihres JDK etwas Zeit, um die Collectors-Toolklasse zu studieren und sich mit den verschiedenen Methoden vertraut zu machen, die sie bietet. Als nächstes werden wir Lambda-Ausdrücke verwenden, um einige Filter zu implementieren.