Vodič za kolekcionare Jave 8

1. Pregled

U ovom uputstvu proći ćemo kroz Java 8's Collectors, koji se koriste u završnom koraku obrade a Stream.

Ako želite pročitati više o Stream API sam, provjerite ovaj članak.

Ako želite vidjeti kako iskoristiti snagu kolektora za paralelnu obradu, provjerite ovaj projekt.

2. The Stream.collect () Metoda

Stream.collect () jedan je od Java 8-ih Stream APITerminalne metode. Omogućuje nam izvođenje promjenjivih operacija presavijanja (prepakiranje elemenata u neke strukture podataka i primjena neke dodatne logike, njihovo spajanje itd.) Na elementima podataka koji se nalaze u Stream primjer.

Strategija za ovu operaciju pruža se putem Kolektor implementacija sučelja.

3. Kolekcionari

Sve unaprijed definirane implementacije mogu se naći u Kolekcionari razred. Uobičajena je praksa koristiti sljedeći statički uvoz s njima kako bi se povećala čitljivost:

uvezi statički java.util.stream.Collectors. *;

ili samo pojedinačni sakupljači uvoza po vašem izboru:

uvoz statičke java.util.stream.Collectors.toList; uvoz statičke java.util.stream.Collectors.toMap; uvoz statičkog java.util.stream.Collectors.toSet;

U sljedećim ćemo primjerima ponovno upotrijebiti sljedeći popis:

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

3.1. Collectors.toList ()

Izlistati kolektor se može koristiti za prikupljanje svih Stream elementi u a Popis primjer. Važno je upamtiti činjenicu da ne možemo pretpostaviti bilo što posebno Popis provedba ovom metodom. Ako želite imati veću kontrolu nad tim, upotrijebite doZbirka umjesto toga.

Stvorimo a Stream instanca koja predstavlja niz elemenata i sakuplja ih u Popis primjer:

Rezultat popisa = givenList.stream () .collect (toList ());

3.1.1. Collectors.toUnmodifiableList ()

Java 10 je uvela prikladan način za akumuliranje Stream elementi u nemodificibilni Popis:

Rezultat popisa = givenList.stream () .collect (toUnmodifiableList ());

Ako sada pokušamo izmijeniti proizlazitiPopis, dobit ćemo UnsupportedOperationException:

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

3.2. Collectors.toSet ()

Postaviti sakupljač se može koristiti za prikupljanje svih Stream elementi u a Postavi primjer. Važno je upamtiti činjenicu da ne možemo pretpostaviti bilo što posebno Postavi provedba ovom metodom. Ako želimo imati veću kontrolu nad tim, možemo koristiti doZbirka umjesto toga.

Stvorimo a Stream instanca koja predstavlja niz elemenata i sakuplja ih u Postavi primjer:

Postavi rezultat = givenList.stream () .collect (toSet ());

A Postavi ne sadrži ponovljene elemente. Ako naša zbirka sadrži međusobno jednake elemente, oni se pojavljuju u rezultirajućem Postavi samo jednom:

Popis listWithDuplicates = Arrays.asList ("a", "bb", "c", "d", "bb"); Postavi rezultat = listWithDuplicates.stream (). Collect (toSet ()); assertThat (rezultat) .hasSize (4);

3.2.1. Collectors.toUnmodifiableSet ()

Od Jave 10 lako možemo stvoriti neizmjenjivu Postavi koristeći toUnmodifiableSet () kolektor:

Postavi rezultat = givenList.stream () .collect (toUnmodifiableSet ());

Svaki pokušaj izmjene rezultat set završit će s UnsupportedOperationException:

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

3.3. Collectors.toCollection ()

Kao što ste vjerojatno već primijetili, prilikom upotrebe toSet i toList kolekcionari, ne možete pretpostaviti njihove implementacije. Ako želite koristiti prilagođenu implementaciju, morat ćete upotrijebiti doZbirka kolektor s osiguranom kolekcijom po vašem izboru.

Stvorimo a Stream instanca koja predstavlja niz elemenata i sakuplja ih u LinkedList primjer:

Rezultat popisa = givenList.stream () .collect (toCollection (LinkedList :: new))

Primijetite da ovo neće raditi ni s jednom nepromjenjivom zbirkom. U tom biste slučaju trebali napisati običaj Kolektor primjena ili upotreba prikupljanjeA onda.

3.4. Kolekcionari.toMap ()

ToMap sakupljač se može koristiti za sakupljanje Stream elementi u a Karta primjer. Da bismo to učinili, moramo osigurati dvije funkcije:

  • keyMapper
  • valueMapper

keyMapper služit će za vađenje a Karta tipka iz a Stream element i valueMapper koristit će se za izdvajanje vrijednosti povezane s danim ključem.

Sakupimo te elemente u a Karta koji sprema nizove kao ključeve i njihove duljine kao vrijednosti:

Rezultat karte = givenList.stream () .collect (toMap (Function.identity (), String :: length))

Funkcija.identitet () je samo prečac za definiranje funkcije koja prihvaća i vraća istu vrijednost.

Što se događa ako naša zbirka sadrži dvostruke elemente? Protivno postaviti, toMap ne filtrira tiho duplikate. Razumljivo je - kako bi trebalo shvatiti koju vrijednost odabrati za ovaj ključ?

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

Imajte na umu da toMap čak ni ne procjenjuje jesu li vrijednosti također jednake. Ako vidi duplicirane ključeve, odmah baca znak IllegalStateException.

U takvim slučajevima kod sudara ključeva trebali bismo koristiti toMap s drugim potpisom:

Rezultat karte = givenList.stream () .collect (toMap (Function.identity (), String :: length, (item, identičniItem) -> item));

Treći argument ovdje je a BinaryOperator, gdje možemo odrediti kako želimo da se rješavaju sudari. U ovom ćemo slučaju samo odabrati bilo koju od ove dvije sudarajuće vrijednosti, jer znamo da će i iste žice uvijek imati iste duljine.

3.4.1. Collectors.toUnmodifiableMap ()

Slično kao i za Popiss i Postavis, Java 10 predstavila je jednostavan način prikupljanja Stream elementi u nemodificibilni Karta:

Rezultat karte = givenList.stream () .collect (toMap (Function.identity (), String :: length))

Kao što vidimo, ako pokušamo staviti novi unos u karta rezultata, dobit ćemo UnsupportedOperationException:

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

3.5. Kolekcionari.collectingAndThen ()

PrikupljanjeA onda je specijalni sakupljač koji omogućuje izvođenje nove radnje na rezultatu odmah nakon završetka sakupljanja.

Skupljajmo Stream elementi na a Popis instance, a zatim rezultat pretvorite u Nepromjenjiva lista primjer:

Rezultat popisa = givenList.stream () .collect (prikupljanjeAndThen (toList (), ImmutableList :: copyOf))

3.6. Kolekcionari.joining ()

Pridruživanje kolektor se može koristiti za spajanje Stream elementi.

Možemo im se pridružiti na sljedeći način:

Rezultat niza = givenList.stream () .collect (joining ());

što će rezultirati:

"abbcccdd"

Također možete odrediti prilagođene separatore, prefikse, postfikse:

Rezultat niza = givenList.stream () .collect (pridruživanje (""));

što će rezultirati:

"bb ccc dd"

ili možete napisati:

Rezultat niza = givenList.stream () .collect (spajanje ("", "PRE-", "-POST"));

što će rezultirati:

"PRE-a bb ccc dd-POST"

3.7. Kolekcionari.cizlazak ()

Brojanje je jednostavan sakupljač koji omogućuje jednostavno brojanje svih Stream elementi.

Sada možemo napisati:

Dugi rezultat = givenList.stream () .collect (counting ());

3.8. Kolekcionari.summarizingDouble / Long / Int ()

SummarisingDouble / Long / Int je sakupljač koji vraća posebnu klasu koja sadrži statističke podatke o numeričkim podacima u a Stream izvađenih elemenata.

Informacije o duljinama nizova možemo dobiti na način da:

Rezultat DoubleSummaryStatistics = givenList.stream () .collect (summarizingDouble (String :: length));

U ovom slučaju vrijedi sljedeće:

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

3.9. Collectors.averagingDouble / Long / Int ()

ProsječenjeDvostruko / Dugo / Int je kolektor koji jednostavno vraća prosjek izvađenih elemenata.

Prosječnu duljinu niza možemo dobiti na način da:

Dvostruki rezultat = givenList.stream () .collect (averagingDouble (String :: length));

3.10. Kolekcionari.summingDouble / Long / Int ()

SummingDouble / Long / Int je sakupljač koji jednostavno vraća zbroj izvađenih elemenata.

Zbroj svih duljina niza možemo dobiti na način da:

Dvostruki rezultat = givenList.stream () .collect (summingDouble (String :: length));

3.11. Kolekcionari.maxBy () / minBy ()

MaxBy/MinBy kolektori vraćaju najveći / najmanji element a Stream prema predviđenom Usporednik primjer.

Najveći element možemo odabrati na način da:

Izborni rezultat = givenList.stream () .collect (maxBy (Comparator.naturalOrder ()));

Primijetite da je vraćena vrijednost umotana u Neobvezno primjer. To prisiljava korisnike da preispitaju prazno kućište kuta za prikupljanje.

3.12. Kolekcionari.grupiranjeBy ()

GrupiranjeBy kolektor se koristi za grupiranje objekata po nekim svojstvima i spremanje rezultata u a Karta primjer.

Možemo ih grupirati prema duljini niza i pohraniti rezultate grupiranja u Postavi primjerci:

Karta rezultat = givenList.stream () .collect (groupingBy (String :: length, toSet ()));

To će rezultirati istinom sljedećeg:

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

Primijetite da je drugi argument grupiranjeBy metoda je a Kolektor i možete slobodno koristiti bilo koji Kolektor po vašem izboru.

3.13. Collectors.partitioningBy ()

PregrađivanjeBy je specijalizirani slučaj za grupiranjeBy koja prihvaća a Predikat instance i prikuplja Stream elementi u a Karta primjer koji pohranjuje Booleova vrijednosti kao ključevi i zbirke kao vrijednosti. Pod tipkom "true" možete pronaći zbirku elemenata koji odgovaraju zadanom Predikat, a pod tipkom „false“ možete pronaći zbirku elemenata koji se ne podudaraju s danim Predikat.

Možete napisati:

Karta rezultat = givenList.stream () .collect (particioniranjeBy (s -> s.length ()> 2))

Što rezultira mapom koja sadrži:

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

3.14. Kolekcionari.teeing ()

Nađimo maksimalni i minimalni broj iz datog Stream pomoću kolektora koje smo do sada naučili:

Brojevi popisa = Arrays.asList (42, 4, 2, 24); Neobvezno min = numbers.stream (). Collect (minBy (Integer :: compareTo)); Po izboru max = numbers.stream (). Collect (maxBy (Integer :: compareTo)); // učinite nešto korisno s min i max

Ovdje koristimo dva različita kolektora, a zatim kombiniramo rezultat ta dva kako bismo stvorili nešto značajno. Prije Jave 12, da bismo pokrili takve slučajeve korištenja, morali smo operirati na danom Stream dva puta pohranite srednje rezultate u privremene varijable, a zatim ih kombinirajte.

Srećom, Java 12 nudi ugrađeni kolektor koji se u naše ime brine za ove korake: sve što moramo učiniti je osigurati dva kolektora i funkciju kombinirača.

Budući da ovaj novi kolektor usmjerava zadani tok u dva različita smjera, zove se teeing:

numbers.stream (). collect (teeing (minBy (Integer :: compareTo), // Prvi sakupljač maxBy (Integer :: compareTo), // Drugi sakupljač (min, max) -> // Prima rezultat od onih sakupljači i kombinira ih));

Ovaj je primjer dostupan na GitHub-u u projektu core-java-12.

4. Prikupljači po mjeri

Ako želite napisati svoju Collector implementaciju, morate implementirati Collector sučelje i navesti njegova tri generička parametra:

sakupljač javnog sučelja {...}
  1. T - vrsta predmeta koji će biti dostupni za prikupljanje,
  2. A - vrsta promjenjivog akumulatorskog objekta,
  3. R - vrsta konačnog rezultata.

Napišimo primjer kolektora za prikupljanje elemenata u ImmutableSet primjer. Počinjemo određivanjem pravih vrsta:

privatna klasa ImmutableSetCollector implementira Collector {...}

Budući da nam je potrebna promjenjiva zbirka za rukovanje internim postupcima prikupljanja, ne možemo je koristiti ImmutableSet za ovo; trebamo koristiti neku drugu promjenjivu kolekciju ili bilo koju drugu klasu koja bi mogla privremeno nakupiti predmete za nas.

U ovom ćemo slučaju nastaviti s ImmutableSet.Builder i sada moramo implementirati 5 metoda:

  • Dobavljačdobavljač()
  • BiConsumerakumulator()
  • BinaryOperatorkombinirač()
  • Funkcijafinišer()
  • Postavi karakteristike()

Dobavljač()metoda vraća a Dobavljač instanci koja generira praznu instancu akumulatora, pa u ovom slučaju možemo jednostavno napisati:

@Preuzmi javnog dobavljača dobavljač () {return ImmutableSet :: builder; } 

Akumulator () metoda vraća funkciju koja se koristi za dodavanje novog elementa postojećem akumulator objekt, pa iskoristimo samo Graditelj‘S dodati metoda.

@Preuzmi javni BiConsumer akumulator () {return ImmutableSet.Builder :: add; }

Kombinator ()metoda vraća funkciju koja se koristi za spajanje dva akumulatora zajedno:

@Preuzmi javni BinaryOperator combiner () {return (lijevo, desno) -> left.addAll (right.build ()); }

Dovršivač () metoda vraća funkciju koja se koristi za pretvaranje akumulatora u konačni tip rezultata, pa ćemo u ovom slučaju samo koristiti Graditelj‘S izgraditi metoda:

@Preuzmi javnu funkciju finisher () {return ImmutableSet.Builder :: build; }

Karakteristike () metoda koristi se za pružanje Streamu nekih dodatnih informacija koje će se koristiti za unutarnju optimizaciju. U ovom slučaju ne obraćamo pažnju na redoslijed elemenata u a Postavi tako da ćemo koristiti Karakteristike.UNORDERED. Da biste dobili više informacija o ovoj temi, provjerite Karakteristike‘JavaDoc.

@Override public Set karakteristike () {return Sets.immutableEnumSet (Characteristics.UNORDERED); }

Evo kompletne implementacije zajedno s upotrebom:

javna klasa ImmutableSetCollector implementira Collector {@Preuzmi javnog dobavljača dobavljač () {return ImmutableSet :: builder; } @Preuzmi javni BiConsumer akumulator () {return ImmutableSet.Builder :: add; } @Override javni BinaryOperator combiner () {return (lijevo, desno) -> left.addAll (right.build ()); } @Preuzmi javnu funkciju finisher () {return ImmutableSet.Builder :: build; } @Override public Set karakteristike () {return Sets.immutableEnumSet (Characteristics.UNORDERED); } javni statički ImmutableSetCollector toImmutableSet () {vratiti novi ImmutableSetCollector (); }

i ovdje na djelu:

Popis givenList = Arrays.asList ("a", "bb", "ccc", "dddd"); Rezultat ImmutableSet = givenList.stream () .collect (toImmutableSet ());

5. Zaključak

U ovom smo članku detaljno istražili Javu 8 Kolekcionari i pokazao kako primijeniti jedan. Obavezno provjerite jedan od mojih projekata koji poboljšava mogućnosti paralelne obrade u Javi.

Svi primjeri koda dostupni su na GitHubu. Na mojoj stranici možete pročitati još zanimljivih članaka.