Vejledning til Java 8's Collectors

1. Oversigt

I denne vejledning gennemgår vi Java 8's Collectors, som bruges i det sidste trin i behandlingen af ​​a Strøm.

Hvis du vil læse mere om Strøm API selv, tjek denne artikel.

Hvis du vil se, hvordan du udnytter effekten af ​​Collectors til parallel behandling, skal du kontrollere dette projekt.

2. Den Stream.collect () Metode

Stream.collect () er en af ​​Java 8'erne Stream API'S terminalmetoder. Det giver os mulighed for at udføre mutable foldoperationer (ompakning af elementer til nogle datastrukturer og anvendelse af yderligere logik, sammenkædning af dem osv.) På dataelementer, der holdes i en Strøm eksempel.

Strategien for denne operation leveres via Samler interface implementering.

3. Samlere

Alle foruddefinerede implementeringer kan findes i Samlere klasse. Det er en almindelig praksis at bruge følgende statiske import med dem for at udnytte øget læsbarhed:

importer statisk java.util.stream.Collectors. *;

eller bare en enkelt importopsamler efter eget valg:

importer statisk java.util.stream.Collectors.toList; importer statisk java.util.stream.Collectors.toMap; importer statisk java.util.stream.Collectors.toSet;

I de følgende eksempler vil vi genbruge følgende liste:

Liste givenList = Arrays.asList ("a", "bb", "ccc", "dd");

3.1. Collectors.toList ()

ToListe samler kan bruges til at indsamle alle Strøm elementer i en Liste eksempel. Det vigtige at huske er, at vi ikke kan antage noget særligt Liste implementering med denne metode. Hvis du vil have mere kontrol over dette, skal du bruge til Samling i stedet.

Lad os oprette en Strøm eksempel, der repræsenterer en sekvens af elementer og samler dem i en Liste eksempel:

Listeresultat = givenList.stream () .collect (toList ());

3.1.1. Collectors.toUnmodifiableList ()

Java 10 introducerede en bekvem måde at akkumulere Strøm elementer til en umodificerbar Liste:

Listeresultat = givenList.stream () .collect (toUnmodifiableList ());

Hvis vi nu prøver at ændre resultatListe, vi får en Ikke-understøttetOperationException:

assertThatThrownBy (() -> result.add ("foo")) .isInstanceOf (UnsupportedOperationException.class);

3.2. Collectors.toSet ()

At sætte samler kan bruges til at indsamle alle Strøm elementer i en Sæt eksempel. Det vigtige at huske er, at vi ikke kan antage noget særligt Sæt implementering med denne metode. Hvis vi vil have mere kontrol over dette, kan vi bruge til Samling i stedet.

Lad os oprette en Strøm forekomst, der repræsenterer en sekvens af elementer og samler dem i en Sæt eksempel:

Indstil resultat = givenList.stream () .collect (toSet ());

EN Sæt indeholder ikke duplikatelementer. Hvis vores samling indeholder elementer, der er lig med hinanden, vises de i det resulterende Sæt kun én gang:

List listWithDuplicates = Arrays.asList ("a", "bb", "c", "d", "bb"); Indstil resultat = listWithDuplicates.stream (). Collect (toSet ()); assertThat (resultat) .hasSize (4);

3.2.1. Collectors.toUnmodifiableSet ()

Siden Java 10 kan vi nemt oprette en umodificerbar Sæt ved brug af toUnmodifiableSet () samler:

Sæt resultat = givenList.stream () .collect (toUnmodifiableSet ());

Ethvert forsøg på at ændre resultat Sæt ender med Ikke-understøttetOperationException:

assertThatThrownBy (() -> result.add ("foo")) .isInstanceOf (UnsupportedOperationException.class);

3.3. Collectors.toCollection ()

Som du sikkert allerede har bemærket, når du bruger toSet og toList samlere, kan du ikke antage nogen antagelser om deres implementeringer. Hvis du vil bruge en brugerdefineret implementering, skal du bruge til Samling samler med en forudsat samling efter eget valg.

Lad os oprette en Strøm forekomst, der repræsenterer en sekvens af elementer og samler dem i en LinkedList eksempel:

Listeresultat = givenList.stream () .collect (toCollection (LinkedList :: ny))

Bemærk, at dette ikke fungerer med uforanderlige samlinger. I et sådant tilfælde skal du enten skrive en brugerdefineret Samler implementering eller anvendelse collectAndThen.

3.4. Samlere.at kortlægge()

At kortlægge samler kan bruges til at indsamle Strøm elementer i en Kort eksempel. For at gøre dette skal vi give to funktioner:

  • keyMapper
  • valueMapper

keyMapper vil blive brugt til udpakning af en Kort nøgle fra en Strøm element og valueMapper vil blive brugt til at udtrække en værdi, der er knyttet til en given nøgle.

Lad os samle disse elementer i en Kort der gemmer strenge som nøgler og deres længder som værdier:

Kortresultat = givenList.stream () .collect (toMap (Function.identity (), String :: længde))

Funktion.identitet () er bare en genvej til at definere en funktion, der accepterer og returnerer den samme værdi.

Hvad sker der, hvis vores samling indeholder duplikatelementer? I modsætning til at sætte, at kortlægge filtrerer ikke lyd duplikater lydløst. Det er forståeligt - hvordan skal det finde ud af, hvilken værdi der skal vælges til denne nøgle?

List listWithDuplicates = Arrays.asList ("a", "bb", "c", "d", "bb"); assertThatThrownBy (() -> {listWithDuplicates.stream (). collect (toMap (Function.identity (), String :: length));}). isInstanceOf (IllegalStateException.class);

Noter det at kortlægge vurderer ikke engang, om værdierne også er ens. Hvis den ser duplikatnøgler, kaster den straks en IllegalStateException.

I sådanne tilfælde med nøglekollision skal vi bruge at kortlægge med en anden underskrift:

Kortresultat = givenList.stream () .collect (toMap (Function.identity (), String :: længde, (item, identiskItem) -> item));

Det tredje argument her er en BinaryOperator, hvor vi kan specificere, hvordan vi ønsker, at kollisioner skal håndteres. I dette tilfælde vælger vi bare en af ​​disse to kolliderende værdier, fordi vi ved, at de samme strenge også altid vil have de samme længder.

3.4.1. Collectors.toUnmodifiableMap ()

Tilsvarende som for Listes og Sæts, Java 10 introducerede en nem måde at indsamle på Strøm elementer til en umodificerbar Kort:

Kortresultat = givenList.stream () .collect (toMap (Function.identity (), String :: længde))

Som vi kan se, hvis vi prøver at sætte en ny post i en resultat Kort, vi får Ikke-understøttetOperationException:

assertThatThrownBy (() -> result.put ("foo", 3)) .isInstanceOf (UnsupportedOperationException.class);

3.5. Samlere.collectingAndThen ()

CollectingAndThen er en speciel samler, der tillader at udføre en anden handling på et resultat lige efter at samlingen er slut.

Lad os indsamle Strøm elementer til en Liste eksempel, og konverter derefter resultatet til et ImmutableList eksempel:

Listeresultat = givenList.stream () .collect (collectAndThen (toList (), ImmutableList :: copyOf))

3.6. Samlere.joining ()

Deltager samler kan bruges til sammenføjning Strøm elementer.

Vi kan slutte dem sammen ved at gøre:

Strengresultat = givenList.stream () .collect (sammenføjning ());

hvilket vil resultere i:

"abbcccdd"

Du kan også angive brugerdefinerede separatorer, præfikser, postfixes:

Strengresultat = givenList.stream () .collect (sammenføjning (""));

hvilket vil resultere i:

"en bb ccc dd"

eller du kan skrive:

Strengresultat = givenList.stream () .collect (sammenføjning ("", "PRE-", "-POST"));

hvilket vil resultere i:

"PRE-a bb ccc dd-POST"

3.7. Samlere.counting ()

Tæller er en simpel samler, der giver mulighed for simpelthen at tælle alle Strøm elementer.

Nu kan vi skrive:

Langt resultat = givenList.stream () .collect (counting ());

3.8. Samlere.summarizingDouble / Long / Int ()

Sammenfatning Dobbelt / Lang / Int er en samler, der returnerer en speciel klasse, der indeholder statistiske oplysninger om numeriske data i en Strøm af ekstraherede elementer.

Vi kan få oplysninger om strenglængder ved at gøre:

DoubleSummaryStatistics resultat = givenList.stream () .collect (summarizingDouble (String :: længde));

I dette tilfælde gælder følgende:

assertThat (result.getAverage ()). er EqualTo (2); assertThat (result.getCount ()). er EqualTo (4); assertThat (result.getMax ()). er EqualTo (3); assertThat (result.getMin ()). er EqualTo (1); assertThat (result.getSum ()). er EqualTo (8);

3.9. Collectors.averagingDouble / Long / Int ()

Gennemsnit Dobbelt / Lang / Int er en samler, der simpelthen returnerer et gennemsnit af ekstraherede elementer.

Vi kan få gennemsnitlig strenglængde ved at gøre:

Dobbelt resultat = givenList.stream () .collect (averagingDouble (String :: længde));

3.10. Samlere.summingDobbelt / Lang / Int ()

SummingDouble / Long / Int er en samler, der simpelthen returnerer en sum af ekstraherede elementer.

Vi kan få en sum af alle strenglængder ved at gøre:

Dobbelt resultat = givenList.stream () .collect (summingDouble (String :: længde));

3.11. Collectors.maxBy () / minBy ()

MaxBy/MinBy samlere returnerer det største / det mindste element i en Strøm ifølge en forudsat Komparator eksempel.

Vi kan vælge det største element ved at gøre:

Valgfrit resultat = givenList.stream () .collect (maxBy (Comparator.naturalOrder ()));

Bemærk, at returneret værdi er pakket ind i en Valgfri eksempel. Dette tvinger brugerne til at genoverveje den tomme samling hjørnesag.

3.12. Samlere.gruppering af ()

Gruppering af collector bruges til at gruppere objekter af nogle ejendomme, og lagring resulterer i en Kort eksempel.

Vi kan gruppere dem efter strenglængde og gemme grupperingsresultater i Sæt tilfælde:

Kort resultat = givenList.stream () .collect (groupingBy (String :: længde, toSet ()));

Dette vil resultere i, at følgende er sandt:

assertThat (resultat) .containsEntry (1, newHashSet ("a")) .containsEntry (2, newHashSet ("bb", "dd")) .containsEntry (3, newHashSet ("ccc")); 

Bemærk, at det andet argument fra grupperingBy metoden er en Samler og du er fri til at bruge nogen Samler efter eget valg.

3.13. Collectors.partitioningBy ()

Partitionering af er et specialiseret tilfælde af grupperingBy der accepterer en Prædikat forekomst og indsamler Strøm elementer i en Kort eksempel, der gemmer Boolsk værdier som nøgler og samlinger som værdier. Under "sand" -tasten kan du finde en samling af elementer, der matcher det givne Prædikat, og under den "falske" nøgle kan du finde en samling af elementer, der ikke matcher det givne Prædikat.

Du kan skrive:

Kort resultat = givenList.stream () .collect (partitioningBy (s -> s.length ()> 2))

Hvilket resulterer i et kort med:

{false = ["a", "bb", "dd"], true = ["ccc"]} 

3.14. Collectors.teeing ()

Lad os finde det maksimale og minimale antal fra et givet Strøm ved hjælp af de samlere, vi hidtil har lært:

Listenumre = Arrays.asList (42, 4, 2, 24); Valgfri min = numbers.stream (). Collect (minBy (Integer :: CompareTo)); Valgfri max = numbers.stream (). Collect (maxBy (Integer :: CompareTo)); // gør noget nyttigt med min og max

Her bruger vi to forskellige samlere og kombinerer derefter resultatet af disse to for at skabe noget meningsfuldt. Før Java 12 måtte vi operere på det givne for at dække sådanne brugssager Strøm Gem to mellemresultater to gange i midlertidige variabler, og kombiner derefter disse resultater bagefter.

Heldigvis tilbyder Java 12 en indbygget samler, der tager sig af disse trin på vores vegne: alt hvad vi skal gøre er at levere de to samlere og kombinationsfunktionen.

Da denne nye samler tees den givne strøm i to forskellige retninger, kaldes den teeing:

numbers.stream (). collect (teeing (minBy (Integer :: CompareTo), // The first collector maxBy (Integer :: comparTo), // The second collector (min, max) -> // Modtager resultatet fra disse samlere og kombinerer dem));

Dette eksempel er tilgængeligt på GitHub i core-java-12-projektet.

4. Brugerdefinerede samlere

Hvis du vil skrive din Collector-implementering, skal du implementere Collector-interface og angive dens tre generiske parametre:

offentlig grænsefladesamler {...}
  1. T - typen af ​​objekter, der vil være tilgængelige for indsamling
  2. EN - typen af ​​et ændret akkumulatorobjekt
  3. R - typen af ​​det endelige resultat.

Lad os skrive et eksempel Collector til indsamling af elementer i en ImmutableSet eksempel. Vi starter med at specificere de rigtige typer:

privat klasse ImmutableSetCollector implementerer Collector {...}

Da vi har brug for en ændret samling til intern håndtering af indsamlingsoperationer, kan vi ikke bruge den ImmutableSet for det; vi er nødt til at bruge en anden ændret samling eller enhver anden klasse, der midlertidigt kan akkumulere objekter til os.

I dette tilfælde fortsætter vi med en ImmutableSet.Builder og nu skal vi implementere 5 metoder:

  • Leverandørleverandør()
  • BiConsumerakkumulator()
  • BinaryOperatorkombinator()
  • Fungereefterbehandler()
  • Sæt egenskaber()

Leverandøren()metode returnerer a Leverandør forekomst, der genererer en tom akkumulatorforekomst, så i dette tilfælde kan vi simpelthen skrive:

@Override offentlig leverandør leverandør () {return ImmutableSet :: builder; } 

Akkumulatoren () metode returnerer en funktion, der bruges til at føje et nyt element til et eksisterende akkumulator objekt, så lad os bare bruge Bygger'S tilføje metode.

@Override offentlig BiConsumer akkumulator () {returner ImmutableSet.Builder :: tilføj; }

Combiner (()metoden returnerer en funktion, der bruges til at flette to akkumulatorer sammen:

@ Override offentlig BinaryOperator combiner () {return (venstre, højre) -> left.addAll (right.build ()); }

Efterbehandleren () metoden returnerer en funktion, der bruges til at konvertere en akkumulator til det endelige resultat, så i dette tilfælde bruger vi bare Bygger'S bygge metode:

@Override offentlig funktion efterbehandler () {return ImmutableSet.Builder :: build; }

Egenskaberne () Metoden bruges til at give Stream nogle yderligere oplysninger, der vil blive brugt til interne optimeringer. I dette tilfælde er vi ikke opmærksomme på elementernes rækkefølge i a Sæt så vi vil bruge Karakteristika. UORDRET. For at få flere oplysninger om dette emne, tjek Egenskaber‘JavaDoc.

@ Override public Sæt karakteristika () {return Sets.immutableEnumSet (Characteristics.UNORDERED); }

Her er den komplette implementering sammen med brugen:

offentlig klasse ImmutableSetCollector implementerer Collector {@Override offentlig leverandør leverandør () {return ImmutableSet :: builder; } @ Override offentlig BiConsumer akkumulator () {returner ImmutableSet.Builder :: tilføj; } @ Override offentlig BinaryOperator combiner () {return (venstre, højre) -> left.addAll (right.build ()); } @ Override offentlig funktion efterbehandler () {return ImmutableSet.Builder :: build; } @ Override public Sæt karakteristika () {return Sets.immutableEnumSet (Characteristics.UNORDERED); } offentlig statisk ImmutableSetCollector tilImmutableSet () {returner ny ImmutableSetCollector (); }

og her i aktion:

Liste givenList = Arrays.asList ("a", "bb", "ccc", "dddd"); ImmutableSet-resultat = givenList.stream () .collect (toImmutableSet ());

5. Konklusion

I denne artikel udforskede vi dybdegående Java 8'er Samlere og viste, hvordan man implementerer en. Sørg for at kontrollere et af mine projekter, der forbedrer mulighederne for parallel behandling i Java.

Alle kodeeksempler er tilgængelige på GitHub. Du kan læse mere interessante artikler på mit websted.