Sammlung, Sammlungen, Sammeln, Sammler, CollectoS
Die Sammlung ist die Vorfahren der Java -Sammlungen.
Collections ist eine Werkzeugklasse unter dem Java.util -Paket, das verschiedene statische Methoden für die Verarbeitung von Sammlungen unterteilt.
java.util.stream.stream#sammeln (java.util.stream.collector <? Super t, a, r>) ist eine Funktion des Streams, der für das Sammeln von Streams verantwortlich ist.
java.util.stream.collector ist eine Schnittstelle zum Sammeln von Funktionen, die die Funktionen eines Sammlers erklärt.
Java.util.comParators ist eine Sammler -Tool -Klasse mit einer Reihe von Einbrüchen von Kollektorimplektoren.
Die Funktion des Sammlers
Sie können sich Java8 -Streams als ausgefallene und faule Datensatz -Iteratoren vorstellen. Sie unterstützen zwei Arten von Operationen: Zwischenoperationen (z. B. Filter, MAP) und Terminaloperationen (z. B. Zählung, Findfirst, Foreach, Reduzierung). Zwischenvorgänge können angeschlossen werden, um einen Stream in einen anderen umzuwandeln. Diese Operationen verbrauchen keine Streams, und es ist der Zweck, eine Pipeline zu erstellen. Im Gegensatz dazu verbrauchen Terminaloperationen Klassen, was zu einem Endergebnis führt. Sammeln ist ein Reduktionsbetrieb, genau wie bei Reduzierung kann es verschiedene Methoden als Parameter akzeptieren und Elemente im Stream in ein zusammenfassendes Ergebnis ansammeln. Der spezifische Ansatz wird durch Definieren einer neuen Sammlerschnittstelle definiert.
Vordefinierte Sammler
Das Folgende ist eine kurze Demonstration des grundlegenden integrierten Sammlers. Die simulierte Datenquelle ist wie folgt:
Final ArrayList <Stich> Dishes = Lists.NewarrayList (New Dish ("Pork", False, 800, Typ. -Meat), neues Gericht ("Rindfleisch", False, 700, Typ.Feat), neues Gericht ("Hühnchen", False, 400, Typ. True, 120, Typ.Other), neues Gericht ("Pizza", wahr, 550, Typ.other), neues Gericht ("Garnelen", False, 300, Typ.fish), neues Gericht ("Lachs", False, 450, Typ.fish));Maximalwert, Minimalwert, Durchschnittswert
// Warum optional zurückkehren? Was tun, wenn der Stream null ist? Optinal macht zu diesem Zeitpunkt optional <Dish> mostcaloriedish = contalory.stream (). Max (vergleicher.comParingint (Dish :: GetCalories)); optional <ist> mincaloriedish tals.stream (). sammeln (sammeln long count = summaryStatistics.getCount (); int max = summaryStatistics.getMax (); int min = summaryStatistics.getmin (); long sum = summaryStatistics.getSum ();
Diese einfachen statistischen Indikatoren haben Sammler integrierte Kollektorfunktionen, insbesondere für numerische Unboxing-Funktionen, die viel kostengünstiger sind als die direkte Bedienung des Verpackungstyps.
Verbinden Sie den Sammler
Möchten Sie die Elemente des Stroms zusammenstellen?
// Schließen Sie String Join1 = Dishes.stream (). Map (Dish :: GetName) .Collect (collectors.joining ()) direkt an.
tolist
List <String> names = distiN.Stream (). Map (Dish :: GetName) .Collect (tolist ());
Zeichnen Sie den ursprünglichen Stream in einen einzelnen Element -Stream und sammeln Sie ihn als Liste.
Toset
Set <Typs> Type = Dishes.Stream (). MAP (DISH :: GETTYPE) .Collect (sammel.toset ());
Sammeln Sie den Typ als Set, und Sie können ihn wiederholen.
Tomap
Karte <Typs, Dish> bytype = contream.stream (). Sammeln (Tomap (Dish :: Gettype, d -> d));
Manchmal kann es notwendig sein, ein Array in eine Karte für Cache umzuwandeln, die mehrere Berechnungen und Akquisitionen erleichtert. Tomap liefert die Erzeugungsfunktionen der Methoden k und v. (Beachten Sie, dass die obige Demo eine Grube ist. Sie können sie nicht so verwenden !!! Bitte verwenden Sie Tomap (Funktion, Funktion, Binaryoperator)).
Die oben genannten sind fast die am häufigsten verwendeten Sammler und sie sind im Grunde genommen genug. Aber als Anfänger braucht Verständnis Zeit. Um wirklich zu verstehen, warum dies zum Sammeln verwendet werden kann, müssen Sie die interne Implementierung überprüfen. Sie können sehen, dass diese Sammler auf java.util.stream.collectors.collectorImpl basieren, einer Implementierungsklasse von Sammler, die zu Beginn erwähnt wird. Der benutzerdefinierte Sammler lernt später die spezifische Verwendung.
Benutzerdefinierte Reduzierung
Die vorherigen wenigen sind Sonderfälle des von der Fabrikmethode reduzierten Reduktionsprozesses. In der Tat kann Collectors.recing verwendet werden, um einen Sammler zu erstellen. Zum Beispiel Summe suchen
Ganzzahl Totalcalories = Dishes.Stream (). Sammeln (Reduktion (0, Dish :: GetCalories, (i, j) -> i + J); // Verwenden Sie eine integrierte Funktion anstelle von Pfeilfunktion Ganzzahl Totalcalories2 = Gerichte.Stream (). Sammeln (0, Schuss :: :: Getcalories, Integer :: Summe);
Natürlich können Sie auch direkt reduzieren
Optional <Gefeger> Totalcalories3 = Dishes.Stream ().
Obwohl es in Ordnung ist, sollten Sie, wenn Sie die Effizienz berücksichtigen, trotzdem Folgendes auswählen
int sum = tals.stream (). maptoint (distel :: getCalories) .sum ();
Wählen Sie die beste Lösung gemäß der Situation
Wie oben erwähnt, bietet die funktionale Programmierung normalerweise mehrere Möglichkeiten, um denselben Betrieb auszuführen. Die Verwendung von Collector Collection ist komplexer als die Verwendung von Stream -APIs. Der Vorteil besteht darin, dass das Sammeln ein höheres Maß an Abstraktion und Generalisierung bieten kann und leichter zuverwenden und anpassen kann.
Unser Rat ist, verschiedene Lösungen für das vorliegende Problem so weit wie möglich zu erkunden, immer die professionellste, was im Allgemeinen die beste Entscheidung in Bezug auf Lesbarkeit und Leistung ist.
Neben dem Erhalt eines Anfangswertes kann die Reduzierung auch den ersten Element als Anfangswert verwenden
Optional <scheuel> mostcaloriedish = cole.stream () .Collect (reduzieren ((d1, d2) -> d1.getCalories ()> d2.getCalories ()? D1: d2));
Reduzieren
Die Verwendung der Reduzierung ist ziemlich kompliziert, und das Ziel ist es, zwei Werte in einen Wert zu verschmelzen.
public static <t, u> kollektor <t,?
Zuerst habe ich 3 Generika gesehen.
U ist die Art des Rückgabewerts. Zum Beispiel ist die in der obigen Demo berechnete Wärme, u Ganzzahl.
In Bezug auf T ist t der Elementtyp im Stream. Aus der Funktionsfunktion können wir wissen, dass die Funktion von Mapper darin besteht, einen Parameter t zu empfangen und dann ein Ergebnis zu erhalten. U. entspricht der Schüssel in der Demo.
In der Mitte der generischen Liste mit dem Rückgabewertkollektor stellt dies den Container -Typ dar. Ein Sammler benötigt natürlich einen Container, um Daten zu speichern. Hier? Dies bedeutet, dass der Containertyp ungewiss ist. Tatsächlich ist der Container hier u [].
Über die Parameter:
Identität ist der Anfangswert des Rückgabewerttyps, der als Ausgangspunkt des Akkumulators verstanden werden kann.
Mapper ist die Funktion der Karte, und seine Bedeutung liegt darin, Stream -Streams in den gewünschten Typ zu konvertieren.
OP ist die Kernfunktion, und seine Funktion ist, wie man mit zwei Variablen umgeht. Unter ihnen ist die erste Variable der kumulative Wert, der als Summe verstanden werden kann, und die zweite Variable ist das nächste zu berechnunges Element. Somit wird die Akkumulation erreicht.
Es gibt auch eine überlastete Methode, um den ersten Parameter wegzulassen, was bedeutet, dass der erste Parameter im Stream als Anfangswert verwendet wird.
öffentliches statisches <t> -Kollektor <t,?, Optional <T >> Reduktion (Binaryoperator <T> op)
Schauen wir uns die Differenz zwischen dem Rückgabewert an. T repräsentiert den Eingangswert und den Rückgabewerttyp, dh der Eingangswerttyp und der Ausgangswerttyp sind gleich. Ein weiterer Unterschied ist optional. Dies liegt daran, dass es keinen Anfangswert gibt und der erste Parameter möglicherweise null ist. Wenn das Stream -Element null ist, ist es sehr sinnvoll, optional zurückzugeben.
Wenn man sich die Parameterliste ansieht, bleibt nur Binaryoperator übrig. Binaryoperator ist eine Dreifachfunktionsschnittstelle. Ziel ist es, zwei Parameter desselben Typs und der gleichen Rückgabewerte desselben Typs zu berechnen. Es kann als 1> 2 verstanden werden? 1: 2, dh den Maximalwert von zwei Zahlen finden. Das Finden des Maximalwerts ist eine relativ leicht zu verständliche Aussage. Sie können den Lambda -Ausdruck anpassen, um den Rückgabewert auszuwählen. Dann soll hier das Elementtyp T von zwei Streams empfangen und den Rückgabewert des Typs T zurückgegeben werden. Es ist auch in Ordnung, SUM zum Verständnis zu verwenden.
In der obigen Demo wird festgestellt, dass die Funktionen von Reduzierung und Sammlung fast gleich sind und beide ein Endergebnis zurückgeben. Zum Beispiel können wir den tolistischen Effekt reduzieren:
// TolistCollector manuell implementieren --- Missbrauch von reduzierten, unveränderlichen Vorschriften --- kann nicht parallele Liste <Integer> Kalorien = Gerichte.Stream (). MAP (DISH :: GetCalories). LIST <NEger> l2) -> {l1.addall (l2);Lassen Sie mich die obigen Praktiken erklären.
<u> u reduzieren (u Identität, Bifunktion <u,? Super t, u> akkumulator, binärer <u> combiner);
U ist der Rückgabewerttyp, hier ist die Liste
Bifunktion <u,? Super T, U> Accumulator ist ein Akkumulator, und sein Ziel ist es, Werte und Berechnungsregeln für einzelne Elemente zu sammeln. Hier ist der Betrieb von Liste und Elementen und schließlich die Rückgabeliste. Fügen Sie der Liste ein Element hinzu.
Binaryoperator <U> Combiner ist ein Kombinierer, und das Ziel ist es, zwei Variablen von Rückgabewerttypen in eine zu verschmelzen. Hier ist der Zusammenschluss von zwei Listen.
Es gibt zwei Probleme mit dieser Lösung: Eine ist ein semantisches Problem und das andere ist ein praktisches Problem. Das semantische Problem besteht darin, dass die Reduzierung der Verringerung zielt darauf ab, zwei Werte zu kombinieren, um einen neuen Wert zu erzeugen, was eine unveränderliche Reduzierung darstellt. Stattdessen besteht das Design der Sammelmethode darin, den Container zu ändern und die zu ausgegebenen Ergebnisse zu sammeln. Dies bedeutet, dass das obige Code -Snippet die Reduzierungsmethode missbraucht, da sie die Liste als Akkumulator ändert. Die falsche Semantik zur Verwendung der Reduzierung von Methoden erzeugt auch ein praktisches Problem: Diese Reduzierung kann nicht parallel funktionieren, da die gleichzeitige Änderung derselben Datenstruktur durch mehrere Threads die Liste selbst zerstören kann. In diesem Fall müssen Sie, wenn Sie die Sicherheit von Thread -Sicherheit wünschen, jeweils eine neue Liste zuweisen, und die Objektzuweisung beeinflusst die Leistung. Aus diesem Grund eignet sich das Sammeln zum Ausdrücken von Reduktionen auf veränderlichen Behältern und vor allem für parallele Operationen.
Zusammenfassung: Die Reduzierung ist für die verringerte Behälterreduktion geeignet. Die Sammlung eignet sich zur Reduzierung der Behälter. Sammeln ist für die Parallelität geeignet.
Gruppierung
Die Datenbank begegnet häufig auf die Notwendigkeit einer Gruppensummierung und stellt die Gruppe für primitive. Wenn Sie in Java dem Anweisungsstil folgen (manuell Schleifen schreiben), ist es sehr umständlich und fehlerhaft. Java 8 liefert funktionale Lösungen.
Zum Beispiel gruppieren Sie Dish nach Typ. Ähnlich wie beim vorherigen Tomap, aber der Gruppierungswert ist kein Gericht, sondern eine Liste.
Karte <type, Liste <Dish >> distesByType = contream.stream (). Sammeln (gruppingBy (disk :: gettype));
Hier
public static <t, k> kollektor <t,?
Der Parameterklassifizierer ist eine Funktion, die so ausgelegt ist, dass sie einen Parameter empfangen und in einen anderen Typ konvertiert. Die obige Demo besteht darin, das Stream -Element -Schale in Typtyp umzuwandeln und dann den Stream entsprechend dem Typ zu gruppieren. Die interne Gruppierung wird durch HashMap implementiert. GroupingBy (Klassifizierer, HashMap :: New, Downstream);
Zusätzlich zur Gruppierung gemäß der Eigenschaftsfunktion des Stream -Elements selbst können Sie auch die Gruppierungsbasis anpassen, z. B. die Gruppierung gemäß dem Wärmebereich.
Da Sie bereits wissen, dass der Parameter der GroupingBy Funktion ist und der Parameter -Funktionstyp von Dish ist, können Sie den Klassifizierer als:
Private Caloriclevel getCaloriclevel (Dish d) {if (d.getCalories () <= 400) {return caloriclevel.diet; } else if (d.getCalories () <= 700) {return caloriclevel.normal; } else {return caloriclevelvel.fat; }}Übergeben Sie einfach die Parameter
Karte <Caloriclevel, Liste <Dish >> DishesbyLevel = Dishes.Stream () .Collect (Gruppingby (this :: getCaloriclevel));
Multi-Level-Gruppierung
Gruppingby überlädt auch mehrere andere Methoden, wie z.
public static <t, k, a, d> kollektor <t,?
Es gibt viele Generika und Schrecken. Lassen Sie uns ein kurzes Verständnis bekommen. Ein Klassifizierer ist auch ein Klassifikator, der den Elementtyp des Streams empfängt und eine Grundlage zurückgibt, für die Sie gruppieren möchten, dh die Kardinalität der Gruppierungsbasis. So repräsentiert T den aktuellen Elementtyp des Streams und K den Elementtyp der Gruppierung darstellt. Der zweite Parameter ist stromabwärts und der Downstream ist ein Kollektorsammler. Dieser Kollektorelementtyp ist eine Unterklasse von T, der Container -Container ist a und der Reduktionsrückgabewerttyp ist D D. Das heißt, der K der Gruppe wird über den Klassifikator bereitgestellt, und der Wert der Gruppe wird durch den Sammler des zweiten Parameters reduziert. Nur so passiert der Quellcode der vorherigen Demo:
public static <t, k> kollektor <t,? }
Verwenden Sie den Tolisten als Reduzierungskollektor, und das Endergebnis ist eine Liste <schus>, sodass der Werttyp der Gruppe endet. Anschließend kann der Werttyp durch den Reduzieren von Kollektor analog bestimmt werden, und es gibt zig Millionen von Reduzieren von Sammlern. Zum Beispiel möchte ich den Wert erneut gruppieren, und die Gruppierung ist auch eine Art Reduzierung.
// Multi -Level -Gruppierungskarte <Typ, Karte <Caloriclevel, Liste <Dish >>> bytypeAndcalory = cole.stream (). Sammeln (gruppingBy (disk :: gettType, gruppingby (this :: getCaloriclevel)); bytypeAndcalory.foreach (Typ, bycalory) -{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ System.out.println("------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- System.out.println ("/t" + Level);Die Überprüfungsergebnisse sind:
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ [Gericht (Name = Rindfleisch, Vegetarier = Falsch, Kalorien = 700, Typ = Fleisch)] -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Typ = andere)]
Zusammenfassung: Die Kernparameter von Groupingby sind K -Generatoren und V -Generatoren. Der V -Generator kann jede Art von Kollektorsammler sein.
Zum Beispiel kann der V -Generator die Nummer berechnen und somit die Auswahl der Anzahl (*) in der SQL -Anweisung aus Tabelle A Gruppe nach Typ implementieren
Karte <Typs, Long> TypeCount = Dishes.stream (). Collect (GroupingBy (Dish :: Gettype, counting ()); System.out.println (TypeCount); ---------- {Fish = 2, Fleisch = 3, andere = 4} SQL -Suchgruppe Höchste Punktzahl select MAX(id) from table A group by Type
Karte <type, optional <Dish >> MostCaloricbytype = Dishes.stream () .Collect (Gruppingby (Dish :: Gettype, Maxby (vergleicher.comParingint (Dish :: GetCalories))));
Optional hier macht keinen Sinn, denn es ist sicherlich nicht null. Dann musste ich es herausnehmen. Mit sammeln und then
Karte <Typs, Dish> Mostcaloricbytype = Dishes.stream () .Collect (GroupingBy (Dish :: GettType, Collecting und Maxby (vergleicher.comParingint (Dish :: GetCalories)), Optional :: Get));
Es scheint, dass das Ergebnis hier rauskommt, aber die Idee stimmt nicht zu. Es kompiliert den gelben Alarm und ändert ihn an:
Karte <Typs, Dish> Mostcaloricbytype = Dishes.stream () .Collect (Tomap (Dish :: Gettype, Funktion.Identity (), binaryoperator.maxby (Vergleiche (Dish :: GetCalories)));
Ja, GroupingBy wird Tomap, Schlüssel ist immer noch Typ, Wert ist immer noch Gericht, aber es gibt noch einen Parameter! ! Hier reagieren wir am Anfang auf die Grube. Die Tomap -Demonstration am Anfang dient zum einfachen Verständnis. Wenn es wirklich verwendet wird, wird es getötet. Wir wissen, dass die Umstrukturierung einer Liste in eine Karte unweigerlich dem gleichen Problem ausgesetzt ist. Wenn k gleich ist, überschreibt oder ignoriert V es dann oder ignoriert es? Die vorherige Demo -Methode besteht darin, K erneut einzufügen und eine Ausnahme direkt auszulegen, wenn k vorhanden ist:
Java.lang.ILLEGALSTATEException: Duplicate Key Dish (Name = Schweinefleisch, Gemüse = Falsch, Kalorien = 800, Typ = Fleisch) bei java.util.stream.collectors.lambda $ ThrowingMerger $ 0 (Sammler.Java:133)
Der richtige Weg besteht darin, Funktionen für den Umgang mit Konflikten bereitzustellen. In dieser Demo besteht das Prinzip der Konflikte darin, das größte zu finden, das nur unsere Anforderungen an die Gruppierung und die Suche nach dem größten entspricht. (Ich möchte wirklich kein funktionelles Lernen mehr machen, ich habe das Gefühl, dass es überall Fallstricke von Leistungsproblemen gibt.)
Setzen Sie die Datenbank SQL Mapping fort, select sum(score) from table a group by Type
Karte <type, Integer> TotalcaloriesByType = Dishes.stream () .Collect (Gruppingby (Dish :: Gettype, Summing (Gericht :: GetCalories)));
Ein weiterer Kollektor, der häufig in Verbindung mit Gruppingby verwendet wird, wird durch die Mapping -Methode erzeugt. Diese Methode empfängt zwei Parameter: Eine Funktion transformiert Elemente im Stream, und die andere sammelt die transformierten Ergebnisobjekte. Ziel ist es, vor der Akkumulation eine Zuordnungsfunktion auf jedes Eingabeelement anzuwenden, damit sich der Kollektor, der Elemente eines bestimmten Typs empfängt, an verschiedene Arten von Objekten anpassen kann. Lassen Sie mich ein praktisches Beispiel für die Verwendung dieses Sammlers ansehen. Sie möchten beispielsweise die Kalorikeln im Menü für jede Art von Schüssel erhalten. Wir können Gruppingby- und Mapping -Sammler wie folgt kombinieren:
Karte <type, set <Caloriclevel >> caloriclevelsByType = cole.stream () .Collect (Gruppingby (Dish :: Gettype, Mapping (this :: getCaloriclevel, toSet ()));
Der Toset hier verwendet das Hashset standardmäßig und Sie können auch die spezifische Implementierung toCollection (Hashset :: New) manuell angeben.
Trennwand
Die Partitionierung ist ein Sonderfall der Gruppierung: Ein Prädikat (eine Funktion, die einen booleschen Wert zurückgibt) wird als Klassifizierungsfunktion verwendet, die als Partitionsfunktion bezeichnet wird. Die Partitionsfunktion gibt einen booleschen Wert zurück, was bedeutet, dass der Schlüsseltyp der gruppierten Karte Boolean ist, sodass sie in bis zu zwei Gruppen unterteilt werden kann: True oder False. Wenn Sie beispielsweise Vegetarier sind, möchten Sie das Menü möglicherweise von vegetarisch und nicht vegetarisch trennen:
Karte <boolean, Liste <Dish >> partitionedMenu = contream.stream (). Sammeln (partitioningby (disk :: isvegetarian));
Die Verwendung von Filter kann natürlich den gleichen Effekt erzielen:
LIST <Ste> vegetarianDishes = Dishes.Stream (). Filter (Dish :: isvegetarian) .Collect (sammel.tolist ());
Der Vorteil der Partitionierung besteht darin, zwei Kopien zu speichern, was nützlich ist, wenn Sie eine Liste klassifizieren möchten. Gleichzeitig hat PartitioningBy wie GruppingBy überlastete Methoden, die die Art des Gruppierungswerts angeben können.
Karte <boolean, Karte <Typ, Liste <Dish >>> vegetariandishesbytype = cole.stream () .Collect (Partitioningby (Dish :: isvegetarian, GroupingBy (Dish :: Gettype)); Karte <boolean, integer> vegetaryDishestotalcalories = rasse.stream (). Summing (Dish :: GetCalories))); Map <boolean, Dish> MostCaloricPartitionedByVegetarian = Dishes.stream () .Collect (Partitioningby (Dish :: isvegetarian, sammeln und sammeln und maxby (vergleiche (Vergleich) (Vergleich :: GetCalories), optional ::
Als letztes Beispiel für die Verwendung des Kollektors für PartitioningBy legen wir das Menüdatenmodell beiseite, um ein komplexeres und interessanteres Beispiel zu sehen: Arrays in Prim- und Nicht-Prime-Zahlen aufzuteilen.
Definieren Sie zunächst eine Prime Partition -Funktion:
private boolean isprime (int Candidate) {int candidateroot = (int) math.sqrt ((doppelter) Kandidat); return intstream.rangecloses (2, candidateroot) .nonematch (i -> kandidat % i == 0);}Finden Sie dann die Prime- und Nicht-Primes-Zahlen von 1 bis 100
Karte <boolean, Liste <Integer >> partitionPrimes = intstream.rangecloses (2, 100) .boxed () .Collect (partitioningby (this :: iSprime));