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 {...}
- T - vrsta predmeta koji će biti dostupni za prikupljanje,
- A - vrsta promjenjivog akumulatorskog objekta,
- 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č() - BiConsumer
akumulator() - BinaryOperator
kombinirač() - Funkcija
finiš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.