Analogy Java 8 Stream API v Kotline

1. Úvod

Java 8 predstavila koncept Prúdy do hierarchie zbierok. Umožňujú veľmi výkonné spracovanie údajov veľmi čitateľným spôsobom s využitím niektorých koncepcií funkčného programovania, aby proces fungoval.

Budeme skúmať, ako môžeme dosiahnuť rovnakú funkcionalitu pomocou Kotlinových idiómov. Ďalej sa pozrieme na funkcie, ktoré nie sú k dispozícii v obyčajnej Jave.

2. Java vs. Kotlin

V prostredí Java 8 je možné nové fantastické rozhranie API použiť iba pri interakcii s programom java.util.stream.Stream inštancie.

Dobrá vec je, že všetky štandardné kolekcie - všetko, čo sa dá implementovať java.util.Collection - mať konkrétnu metódu Prúd() ktoré môžu produkovať a Prúd inštancia.

Je dôležité mať na pamäti, že Prúd nie je Zbierka.Nerealizuje java.util.Collection a neimplementuje žiadnu z bežných sémantík jazyka Zbierky v Jave. Je to skôr podobné jednorazu Iterátor v tom, že je odvodené od a Zbierka a používa sa na jeho prepracovanie a vykonávanie operácií s každým viditeľným prvkom.

V Kotline tieto operácie už podporujú všetky typy zbierok bez toho, aby ste ich museli najskôr konvertovať. Konverzia je potrebná iba v prípade, že je sémantika zbierky nesprávna - napr Nastaviť má jedinečné prvky, ale nie je usporiadané.

Výhodou je, že nie je potrebná počiatočná konverzia z a Zbierka do a Prúd, a nie je potrebná konečná konverzia z a Prúd späť do zbierky - pomocou zbierať () hovory.

Napríklad v prostredí Java 8 by sme museli napísať nasledovné:

someList .stream () .map () // niektoré operácie .collect (Collectors.toList ());

Ekvivalent v Kotline je veľmi jednoduchý:

someList .map () // niektoré operácie

Ďalej Java 8 Prúdy sú tiež opakovane nepoužiteľné. Po Prúd je spotrebovaný, nedá sa znovu použiť.

Napríklad nebude fungovať nasledovné:

Streamovať someIntegers = integers.stream (); someIntegers.forEach (...); someIntegers.forEach (...); // výnimka

Skutočnosť, že v Kotline ide iba o bežné zbierky, znamená, že tento problém nikdy nenastane. Medziľahlý stav je možné priradiť k premenným a rýchlo zdieľať, a funguje len tak, ako by sme očakávali.

3. Lenivé sekvencie

Jedna z kľúčových vecí Java 8 Prúdy je to, že sa hodnotia lenivo. To znamená, že sa nevykoná viac práce, ako je potrebné.

To je obzvlášť užitočné, ak robíme potenciálne nákladné operácie s prvkami v Prúd, alebo umožňuje pracovať s nekonečnými sekvenciami.

Napríklad, IntStream.generate vyprodukuje potenciálne nekonečno Prúd celých čísel. Ak zavoláme findFirst () na ňom dostaneme prvý prvok a nenarazíme na nekonečnú slučku.

V Kotline sú zbierky skôr nedočkavé ako lenivé. Výnimka tu je Postupnosť, ktorá hodnotí lenivo.

Je potrebné si uvedomiť toto dôležité rozlíšenie, ako ukazuje nasledujúci príklad:

výsledok výsledku = listOf (1, 2, 3, 4, 5) .map {n -> n * n} .filter {n -> n <10} .first ()

Kotlinova verzia toho predvedie päť mapa () operácie, päť filter () operácie a potom extrahujte prvú hodnotu. Verzia Java 8 bude vykonávať iba jednu mapa () a jeden filter () pretože z pohľadu poslednej operácie už nie sú potrebné ďalšie.

Všetky zbierky v Kotline je možné prevádzať na lenivú sekvenciu pomocou asSequence () metóda.

Pomocou a Postupnosť namiesto a Zoznam vo vyššie uvedenom príklade vykonáva rovnaký počet operácií ako v prostredí Java 8.

4. Java 8 Prúd Operácie

V prostredí Java 8 Prúd operácie sú rozdelené do dvoch kategórií:

  • stredne pokročilý a
  • terminál

Medzistupne v zásade prevádzajú jednu Prúd do iného lenivo - napríklad a Prúd všetkých celých čísel do a Prúd všetkých párnych celých čísel.

Možnosti terminálu sú posledným krokom v Prúd reťazec metód a spustiť samotné spracovanie.

V Kotline nie je také rozlíšenie. Namiesto toho to všetko sú iba funkcie, ktoré berú kolekciu ako vstup a vytvárajú nový výstup.

Všimnite si, že ak v Kotline používame dychtivú zbierku, potom sa tieto operácie vyhodnotia okamžite, čo môže byť v porovnaní s Javou prekvapujúce. Ak potrebujeme, aby to bolo lenivé, nezabudnite previesť na a Postupnosť najprv.

4.1. Sprostredkujúce operácie

Takmer všetky prechodné operácie z rozhrania Java 8 Streams API majú v Kotline ekvivalenty. Nejde však o medzioperačné operácie - s výnimkou prípadu Postupnosť trieda - pretože výsledkom sú plne obsadené kolekcie zo spracovania vstupnej kolekcie.

Z týchto operácií existuje niekoľko, ktoré fungujú úplne rovnako - filter (), mapa (), flatMap (), odlišný() a zoradené () - a niektoré, ktoré fungujú rovnako, len s rôznymi názvami - limit () je teraz vziaťa preskočiť () je teraz pokles(). Napríklad:

val oddSquared = listOf (1, 2, 3, 4, 5). filter {n -> n% 2 == 1} // 1, 3, 5 .map {n -> n * n} // 1, 9 , 25 .drop (1) // 9, 25. Take (1) // 9

Takto sa vráti jediná hodnota „9“ - 3².

Niektoré z týchto operácií majú aj ďalšiu verziu - doplnenú o slovo „To“ - ktoré sa vydávajú do poskytnutej zbierky namiesto produkcie novej.

To môže byť užitočné pri spracovaní viacerých vstupných kolekcií do tej istej výstupnej kolekcie, napríklad:

val target = mutableList () listOf (1, 2, 3, 4, 5) .filterTo (target) {n -> n% 2 == 0}

Týmto vložíte hodnoty „2“ a „4“ do zoznamu „cieľ“.

Jedinou operáciou, ktorá zvyčajne nemá priamu náhradu, je nahliadnuť () - používaný v Java 8 na iteráciu nad položkami v Prúd uprostred spracovateľského potrubia bez prerušenia toku.

Ak používame lenivého Postupnosť namiesto nedočkavej zbierky potom existuje na každom() funkcia, ktorá priamo nahrádza nakuknúť funkcie. Toto však existuje iba v tejto jednej triede, a preto musíme vedieť, aký typ používame, aby fungoval.

Existujú aj niektoré ďalšie variácie štandardných medzioperácií, ktoré uľahčujú život. Napríklad filter prevádzka má ďalšie verzie filterNotNull (), filterIsInstance (), filterNot () a filterIndexed ().

Napríklad:

listOf (1, 2, 3, 4, 5) .map {n -> n * (n + 1) / 2} .mapIndexed {(i, n) -> "Trojuholníkové číslo $ i: $ n"}

Toto vyprodukuje prvých päť trojuholníkových čísel v tvare „Trojuholníkové číslo 3: 6“.

Ďalším dôležitým rozdielom je spôsob, akým flatMap prevádzka funguje. V prostredí Java 8 sa táto operácia vyžaduje na vrátenie a Prúd napríklad v Kotline môže vrátiť akýkoľvek typ zbierky. To uľahčuje prácu.

Napríklad:

val letters = listOf ("This", "Is", "An", "Example") .flatMap {w -> w.toCharArray ()} // Vytvorí zoznam .filter {c -> Character.isUpperCase (c) }

V prostredí Java 8 by bolo treba zabaliť druhý riadok Arrays.toStream () aby to fungovalo.

4.2. Prevádzka terminálu

Všetky štandardné operácie terminálu z rozhrania Java 8 Streams API majú priame nahradenie v Kotline, s jedinou výnimkou zbierať.

Pár z nich má rôzne mená:

  • anyMatch () ->akýkoľvek()
  • allMatch () ->všetko ()
  • noneMatch () ->žiadny ()

Niektoré z nich majú ďalšie variácie, aby pracovali s tým, ako má Kotlin rozdiely - existujú najprv() a firstOrNull (), kde najprv hodí, ak je kolekcia prázdna, ale inak vráti typ s nulovou hodnotou.

Zaujímavý prípad je zbierať. Java 8 to využíva na to, aby mohla zhromaždiť všetky Prúd prvky do nejakého zberu pomocou poskytnutej stratégie.

To umožňuje svojvoľnosť Zberateľ ktoré budú poskytnuté so všetkými prvkami v zbierke a budú produkovať výstup nejakého druhu. Tieto sa používajú z Zberatelia pomocná trieda, ale v prípade potreby si môžeme napísať aj vlastnú.

V Kotline existujú priame náhrady za takmer všetky štandardné kolektory dostupné priamo ako členovia v samotnom objekte kolekcie - nie je potrebný ďalší krok so zabezpečeným zberačom.

Jedinou výnimkou je tu zhrnutieDouble/summarizingInt/sumarizujúciDlhé metódy - ktoré produkujú priemer, počet, min., max. a súčet naraz. Každý z nich sa dá vyrobiť individuálne - aj keď to má samozrejme vyššiu cenu.

Prípadne to môžeme spravovať pomocou slučky for-each a v prípade potreby ju zvládnuť ručne - je nepravdepodobné, že budeme potrebovať všetkých 5 týchto hodnôt súčasne, takže musíme implementovať iba tie, ktoré sú dôležité.

5. Dodatočné operácie v Kotline

Kotlin pridáva do zbierok ďalšie operácie, ktoré nie sú možné v prostredí Java 8 bez toho, aby sme ich sami implementovali.

Niektoré z nich sú iba rozšírením štandardných operácií, ako je opísané vyššie. Napríklad je možné vykonať všetky operácie tak, že sa výsledok pridá do existujúcej kolekcie namiesto vrátenia novej kolekcie.

V mnohých prípadoch je tiež možné nechať lambdu vybaviť nielen príslušným prvkom, ale aj indexom prvku - pre kolekcie, ktoré sú usporiadané, a preto majú indexy zmysel.

Existujú aj niektoré operácie, ktoré výslovne využívajú nulovú bezpečnosť Kotlina - napríklad; môžeme vykonať a filterNotNull () na a Zoznam vrátiť sa a Zoznam, kde sú odstránené všetky nulové hodnoty.

Skutočné ďalšie operácie, ktoré je možné vykonať v Kotline, ale nie v tokoch Java 8, zahŕňajú:

  • PSČ() a rozbaliť () - slúžia na spojenie dvoch zbierok do jednej postupnosti párov a naopak na konverziu zbierky párov do dvoch zbierok
  • spolupracovník - slúži na prevod zbierky na mapu poskytnutím lambda na prevod každej položky v zbierke na pár kľúč / hodnota vo výslednej mape

Napríklad:

val numbers = listOf (1, 2, 3) val words = listOf ("one", "two", "three") numbers.zip (words)

Týmto sa získa a Zoznam, s hodnotami 1 až „jeden“, 2 až „dva“ a 3 až „tri“.

val squares = listOf (1, 2, 3, 4,5). asociujte {n -> n až n * n}

Týmto sa získa a Mapa, kde klávesy sú číslice 1 až 5 a hodnoty sú štvorce týchto hodnôt.

6. Zhrnutie

Väčšina streamovacích operácií, na ktoré sme zvyknutí z Javy 8, je priamo použiteľná v Kotline na štandardných triedach Collection, bez nutnosti prevádzania na Prúd najprv.

Okrem toho Kotlin pridáva viac flexibility do toho, ako to funguje, pridaním ďalších operácií, ktoré je možné použiť, a väčšej variácie existujúcich operácií.

Kotlin je však štandardne nedočkavý, nie lenivý. To môže spôsobiť vykonanie ďalších prác, ak si nebudeme dávať pozor na typy kolekcií, ktoré sa používajú.