Sprievodca zberateľmi Java 8’s

1. Prehľad

V tomto výučbe si prejdeme zberače Java 8, ktoré sa používajú v poslednom kroku spracovania a Prúd.

Ak si chcete prečítať viac o Prúd Samotné API, pozrite si tento článok.

Ak chcete zistiť, ako využiť silu kolektorov na paralelné spracovanie, pozrite si tento projekt.

2. The Stream.collect () Metóda

Stream.collect () je jedným z Java 8 Stream APITerminálne metódy. Umožňuje nám to vykonávať operácie premenlivého zloženia (prebalenie prvkov do niektorých dátových štruktúr a použitie nejakej ďalšej logiky, ich zreťazenie atď.) Na dátových prvkoch uchovávaných v Prúd inštancia.

Stratégia pre túto operáciu je poskytovaná prostredníctvom Zberateľ implementácia rozhrania.

3. Zberatelia

Všetky preddefinované implementácie nájdete v dokumente Zberatelia trieda. Je bežnou praxou používať nasledujúci statický import, aby sa zvýšila čitateľnosť:

importovať statický java.util.stream.Collectors. *;

alebo len zberatelia jedného importu podľa vášho výberu:

importovať statický java.util.stream.Collectors.toList; importovať statický java.util.stream.Collectors.toMap; importovať statický java.util.stream.Collectors.toSet;

V nasledujúcich príkladoch budeme opakovane používať nasledujúci zoznam:

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

3.1. Collectors.toList ()

Listovať zberač možno použiť na zber všetkých Prúd prvky do a Zoznam inštancia. Je dôležité pamätať na skutočnosť, že nemôžeme predpokladať nič konkrétne Zoznam implementácia touto metódou. Ak nad tým chcete mať väčšiu kontrolu, použite do zbierky namiesto toho.

Vytvorme a Prúd inštancia predstavujúca postupnosť prvkov a zhromaždiť ich do a Zoznam inštancia:

Výsledok zoznamu = givenList.stream () .collect (toList ());

3.1.1. Collectors.toUnmodifiableList ()

Java 10 predstavila pohodlný spôsob akumulácie Prúd prvkov na neupraviteľné Zoznam:

Výsledok zoznamu = givenList.stream () .collect (toUnmodifiableList ());

Ak sa teraz pokúsime upraviť výsledokZoznam, dostaneme UnsupportedOperationException:

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

3.2. Collectors.toSet ()

Nastaviť zberač možno použiť na zber všetkých Prúd prvky do a Nastaviť inštancia. Je potrebné pamätať na skutočnosť, že nemôžeme predpokladať nič konkrétne Nastaviť implementácia touto metódou. Ak nad tým chceme mať väčšiu kontrolu, môžeme použiť do zbierky namiesto toho.

Vytvorme a Prúd inštancia predstavujúca postupnosť prvkov a zhromaždiť ich do a Nastaviť inštancia:

Nastaviť výsledok = givenList.stream () .collect (toSet ());

A Nastaviť neobsahuje duplicitné prvky. Ak naša kolekcia obsahuje navzájom rovnaké prvky, objavia sa vo výsledkoch Nastaviť iba raz:

Zoznam listWithDuplicates = Arrays.asList ("a", "bb", "c", "d", "bb"); Nastaviť výsledok = listWithDuplicates.stream (). Collect (toSet ()); assertThat (výsledok) .hasSize (4);

3.2.1. Collectors.toUnmodifiableSet ()

Od Java 10 môžeme ľahko vytvoriť nemodifikovateľné Nastaviť použitím toUnmodifiableSet () zberateľ:

Nastaviť výsledok = givenList.stream () .collect (toUnmodifiableSet ());

Akýkoľvek pokus o úpravu sada výsledkov skončí s UnsupportedOperationException:

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

3.3. Collectors.toCollection ()

Ako ste si už určite všimli, pri použití toSet a toList zberatelia, nemôžete urobiť nijaké predpoklady o ich implementácii. Ak chcete použiť vlastnú implementáciu, budete musieť použiť do zbierky zberateľ s poskytnutou zbierkou podľa vášho výberu.

Vytvorme a Prúd inštancia predstavujúca postupnosť prvkov a zhromaždiť ich do a LinkedList inštancia:

Výsledok zoznamu = givenList.stream () .collect (toCollection (LinkedList :: new))

Všimnite si, že to nebude fungovať so žiadnymi nemennými zbierkami. V takom prípade by ste museli napísať zvyk Zberateľ implementácia alebo použitie zbieranieA potom.

3.4. Zberatelia.mapovať()

Mapovať zberač možno použiť na zber Prúd prvky do a Mapa inštancia. Aby sme to dosiahli, musíme poskytnúť dve funkcie:

  • keyMapper
  • valueMapper

keyMapper použije sa na vyťaženie a Mapa kľúč od a Prúd prvok a valueMapper sa použije na extrahovanie hodnoty spojenej s daným kľúčom.

Zozbierajme tieto prvky do a Mapa ktorý ukladá reťazce ako kľúče a ich dĺžky ako hodnoty:

Výsledok mapy = givenList.stream () .collect (toMap (Function.identity (), String :: length))

Function.identity () je iba skratka na definovanie funkcie, ktorá prijíma a vracia rovnakú hodnotu.

Čo sa stane, ak naša zbierka obsahuje duplicitné prvky? V rozpore s nastaviť, mapovať nefiltruje ticho duplikáty. Je to pochopiteľné - ako by malo zistiť, ktorú hodnotu pre tento kľúč zvoliť?

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

Poznač si to mapovať ani nevyhodnocuje, či sú aj hodnoty rovnaké. Ak vidí duplicitné kľúče, okamžite hodí znak IllegalStateException.

V takýchto prípadoch s kolíziou kľúčov by sme mali použiť mapovať s iným podpisom:

Výsledok mapy = danýList.stream () .collect (toMap (Function.identity (), String :: dĺžka, (položka, identická položka) -> položka));

Tretím argumentom je a BinaryOperator, kde môžeme určiť, ako chceme, aby sa kolízie riešili. V takom prípade vyberieme ktorúkoľvek z týchto dvoch kolidujúcich hodnôt, pretože vieme, že rovnaké reťazce budú mať vždy rovnako dlhé dĺžky.

3.4.1. Collectors.toUnmodifiableMap ()

Podobne ako pre Zoznams a Nastaviťs, Java 10 predstavila jednoduchý spôsob zberu Prúd prvkov na neupraviteľné Mapa:

Výsledok mapy = givenList.stream () .collect (toMap (Function.identity (), String :: length))

Ako vidíme, ak sa pokúsime vložiť nový záznam do a výsledok mapa, dostaneme UnsupportedOperationException:

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

3.5. Zberatelia.collectingAndThen ()

CollectingAndThen je špeciálny zberač, ktorý umožňuje vykonať ďalšiu akciu s výsledkom hneď po ukončení zberu.

Poďme zbierať Prúd prvky do a Zoznam inštanciu a potom výsledok previesť na ImmutableList inštancia:

Výsledok zoznamu = givenList.stream () .collect (collectAndThen (toList (), ImmutableList :: copyOf))

3.6. Zberatelia.joining ()

Pripojenie sa na spájanie je možné použiť zberač Prúd prvkov.

Môžeme sa k nim pridať tak, že urobíme:

Výsledok reťazca = givenList.stream () .collect (joining ());

ktorého výsledkom bude:

„abbcccdd“

Môžete tiež určiť vlastné oddeľovače, predpony, postfixy:

Výsledok reťazca = givenList.stream () .collect (joining (""));

ktorého výsledkom bude:

„a bb ccc dd“

alebo môžete napísať:

Výsledok reťazca = givenList.stream () .collect (joining ("", "PRE-", "-POST"));

ktorého výsledkom bude:

„PRE-a bb ccc dd-POST“

3.7. Zberatelia.counting ()

Počítanie je jednoduchý zberač, ktorý umožňuje jednoduché spočítanie všetkých Prúd prvkov.

Teraz môžeme napísať:

Dlhý výsledok = givenList.stream () .collect (countting ());

3.8. Zberatelia.summarizingDouble / Long / Int ()

SumarizingDouble / Long / Int je zberač, ktorý vracia špeciálnu triedu obsahujúcu štatistické informácie o číselných údajoch v a Prúd extrahovaných prvkov.

Informácie o dĺžkach reťazcov môžeme získať:

Výsledok DoubleSummaryStatistics = givenList.stream () .collect (summarizingDouble (String :: length));

V takom prípade bude platiť toto:

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 ()

PriemerovanieDouble / Long / Int je zberač, ktorý jednoducho vráti priemer vyťažených prvkov.

Priemernú dĺžku reťazca môžeme získať vykonaním:

Dvojitý výsledok = givenList.stream () .collect (averagingDouble (String :: length));

3.10. Zberatelia.summingDouble / Long / Int ()

SčítanieDouble / Long / Int je kolektor, ktorý jednoducho vráti súčet extrahovaných prvkov.

Súčet všetkých dĺžok reťazcov môžeme získať vykonaním:

Dvojitý výsledok = givenList.stream () .collect (summingDouble (String :: length));

3.11. Collectors.maxBy () / minBy ()

MaxBy/MinBy zberatelia vracajú najväčší / najmenší prvok a Prúd podľa stanoveného Komparátor inštancia.

Najväčší prvok môžeme vybrať vykonaním:

Voliteľný výsledok = givenList.stream () .collect (maxBy (Comparator.naturalOrder ()));

Všimnite si, že vrátená hodnota je zabalená do Voliteľné inštancia. Toto prinúti používateľov prehodnotiť prázdny rohový prípad zbierky.

3.12. Zberatelia.groupingBy ()

Zoskupenie podľa collector sa používa na zoskupovanie objektov podľa niektorých vlastností a ukladanie výsledkov do a Mapa inštancia.

Môžeme ich zoskupiť podľa dĺžky reťazca a výsledky zoskupenia uložiť do Nastaviť prípady:

Mapa result = givenList.stream () .collect (groupingBy (String :: length, toSet ()));

Výsledkom bude nasledujúca pravda:

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

Všimnite si, že druhý argument zoskupenieBy metóda je a Zberateľ a môžete slobodne používať akékoľvek Zberateľ podľa vášho výberu.

3.13. Collectors.partitioningBy ()

Rozdelenie je špecializovaný prípad zoskupenieBy ktorý prijíma a Predikát inštancie a zbiera Prúd prvky do a Mapa inštancia, ktorá ukladá Boolovský hodnoty ako kľúče a kolekcie ako hodnoty. Pod kľúčom „pravda“ nájdete kolekciu prvkov zodpovedajúcich danému Predikát, a pod kľúčom „false“ môžete nájsť zbierku prvkov, ktoré sa nezhodujú s daným Predikát.

Môžeš písať:

Mapa result = givenList.stream () .collect (partitioningBy (s -> s.length ()> 2))

Výsledkom čoho je mapa obsahujúca:

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

3.14. Collectors.teeing ()

Nájdeme maximálny a minimálny počet z daného čísla Prúd pomocou zberačov, ktoré sme sa doteraz naučili:

Zoznam čísel = Arrays.asList (42, 4, 2, 24); Voliteľné min = numbers.stream (). Collect (minBy (Integer :: compareTo)); Voliteľné max = numbers.stream (). Collect (maxBy (Integer :: compareTo)); // urobte niečo užitočné s min a max

Tu používame dva rôzne kolektory a potom kombinujeme výsledok týchto dvoch, aby sme vytvorili niečo zmysluplné. Pred programom Java 12 sme v záujme pokrytia takýchto prípadov použitia museli operovať dané Prúd dvakrát, uložte priebežné výsledky do dočasných premenných a potom tieto výsledky potom skombinujte.

Našťastie Java 12 ponúka vstavaný kolektor, ktorý sa v našom mene stará o tieto kroky: musíme len poskytnúť dva kolektory a funkciu kombinátora.

Pretože tento nový kolektor posúva daný prúd do dvoch rôznych smerov, volá sa odpalisko:

numbers.stream (). collect (teeing (minBy (Integer :: compareTo), // Prvý kolektor maxBy (Integer :: compareTo), // Druhý kolektor (min, max) -> // Výsledok dostane od týchto zberateľov a kombinuje ich));

Tento príklad je k dispozícii na GitHub v projekte core-java-12.

4. Zberatelia na zákazku

Ak chcete napísať svoju implementáciu Collector, musíte implementovať rozhranie Collector a určiť jeho tri všeobecné parametre:

verejné rozhranie Collector {...}
  1. T - typ predmetov, ktoré budú k dispozícii na zber,
  2. A - typ objektu premenlivého akumulátora,
  3. R - druh konečného výsledku.

Poďme si napísať príklad Zberateľ na zhromažďovanie prvkov do ImmutableSet inštancia. Začneme zadaním správnych typov:

súkromná trieda ImmutableSetCollector implementuje Collector {...}

Pretože potrebujeme premenlivú zbierku na interné spracovanie operácie zhromažďovania, nemôžeme ju použiť ImmutableSet pre to; musíme použiť nejakú inú premenlivú zbierku alebo inú triedu, ktorá by pre nás mohla dočasne hromadiť predmety.

V takom prípade budeme pokračovať s ImmutableSet.Builder a teraz musíme implementovať 5 metód:

  • Dodávateľdodávateľ()
  • BiConsumerakumulátor()
  • BinaryOperatorkombinátor()
  • Funkciafinišer()
  • Nastaviť charakteristiky()

Dodávateľ()metóda vracia a Dodávateľ inštancia, ktorá generuje prázdnu inštanciu akumulátora, takže v tomto prípade môžeme jednoducho napísať:

@ Override verejný dodávateľ dodávateľ () {return ImmutableSet :: builder; } 

Akumulátor () metóda vracia funkciu, ktorá sa používa na pridanie nového prvku k existujúcemu akumulátor objekt, tak použijeme iba Staviteľ‘S pridať metóda.

@ Override public BiConsumer akumulátor () {return ImmutableSet.Builder :: add; }

Kombinátor ()metóda vracia funkciu, ktorá sa používa na zlúčenie dvoch akumulátorov dohromady:

@ Override public BinaryOperator combineer () {return (left, right) -> left.addAll (right.build ()); }

Dokončovateľ () metóda vráti funkciu, ktorá sa používa na prevod akumulátora na typ konečného výsledku, takže v tomto prípade použijeme iba Staviteľ‘S stavať metóda:

@ Verejná funkcia override finisher () {return ImmutableSet.Builder :: build; }

Vlastnosti () metóda slúži na to, aby poskytla Streamu nejaké ďalšie informácie, ktoré sa použijú na internú optimalizáciu. V takom prípade nevenujeme pozornosť prvkom poradia v a Nastaviť aby sme použili Vlastnosti. NEZARADENÉ. Ak chcete získať viac informácií o tejto téme, začiarknite políčko Charakteristiky„JavaDoc.

@ Verejné nastavenie verejnej charakteristiky () {return Sets.immutableEnumSet (Characteristics.UNORDERED); }

Tu je kompletná implementácia spolu s použitím:

verejná trieda ImmutableSetCollector implementuje Collector {@Override verejný dodávateľ dodávateľ () {return ImmutableSet :: builder; } @ Override public BiConsumer akumulátor () {return ImmutableSet.Builder :: add; } @Override public BinaryOperator combineer () {return (left, right) -> left.addAll (right.build ()); } @ Verejná funkcia override finisher () {return ImmutableSet.Builder :: build; } @Override public Set characteristics () {return Sets.immutableEnumSet (Characteristics.UNORDERED); } public static ImmutableSetCollector toImmutableSet () {return new ImmutableSetCollector (); }

a tu v akcii:

List givenList = Arrays.asList ("a", "bb", "ccc", "dddd"); Výsledok ImmutableSet = givenList.stream () .collect (toImmutableSet ());

5. Záver

V tomto článku sme preskúmali hĺbkové Java 8 Zberatelia a ukázal, ako ho implementovať. Nezabudnite skontrolovať jeden z mojich projektov, ktorý zvyšuje možnosti paralelného spracovania v Jave.

Všetky príklady kódov sú k dispozícii na GitHub. Ďalšie zaujímavé články si môžete prečítať na mojej stránke.


$config[zx-auto] not found$config[zx-overlay] not found