Java 8 Stream API-analógiák Kotlinban

1. Bemutatkozás

A Java 8 bevezette a Patakok a gyűjteményi hierarchiához. Ezek lehetővé teszik az adatok nagyon hatékony feldolgozását nagyon olvasható módon, néhány funkcionális programozási koncepció felhasználásával a folyamat működéséhez.

Megvizsgáljuk, hogyan érhetjük el ugyanazt a funkcionalitást a Kotlin idiómák használatával. Megnézzük azokat a funkciókat is, amelyek nem érhetők el a sima Java-ban.

2. Java kontra Kotlin

A Java 8-ban az új divatos API csak akkor használható, ha interakcióba lépnek vele java.util.stream.Stream példányok.

A jó dolog az, hogy az összes szokásos gyűjtemény - bármi, ami megvalósítható java.util.Gyűjtés - van egy adott módszere folyam() hogy képes előállítani a Folyam példa.

Fontos megjegyezni, hogy a Folyam nem egy Gyűjtemény.Nem hajtja végre java.util.Gyűjtés és nem hajtja végre a normál szemantikáját Gyűjtemények Java-ban. Ez inkább hasonlít az egyszeri alkalomhoz Iterátor annyiban származik a Gyűjtemény és arra használják, hogy végigdolgozza, műveleteket hajtson végre minden látható elemen.

Kotlinban minden gyűjteménytípus már támogatja ezeket a műveleteket anélkül, hogy először meg kellene őket átalakítani. Átalakításra csak akkor van szükség, ha a gyűjtemény szemantikája téves - például a Készlet egyedi elemekkel rendelkezik, de rendezetlen.

Ennek egyik előnye, hogy nincs szükség a Gyűjtemény ba be Folyam, és nincs szükség a Folyam vissza egy gyűjteménybe - a gyűjt() hívások.

Például a Java 8-ban a következőket kell írnunk:

someList .stream () .map () // néhány művelet .collect (Collectors.toList ());

Kotlin megfelelője nagyon egyszerűen:

someList .map () // néhány művelet

Ezen felül Java 8 Patakok szintén nem használhatók fel újra. Utána Folyam fogyasztják, nem lehet újra felhasználni.

Például a következők nem fognak működni:

Stream someIntegers = integers.stream (); someIntegers.forEach (...); someIntegers.forEach (...); // egy kivétel

Kotlinban az a tény, hogy ezek csak normális gyűjtemények, azt jelenti, hogy ez a probléma soha nem merül fel. Közbenső állapot hozzárendelhető változókhoz és gyorsan megosztható, és csak úgy működik, mint várnánk.

3. Lusta szekvenciák

A Java 8 egyik legfontosabb eleme Patakok az, hogy lustán értékelik őket. Ez azt jelenti, hogy a szükségesnél többet nem végeznek.

Ez különösen akkor hasznos, ha potenciálisan drága műveleteket hajtunk végre a Folyam, vagy lehetővé teszi a végtelen szekvenciákkal való munkát.

Például, IntStream.generate potenciálisan végtelenet fog termelni Folyam egész számok. Ha hívunk findFirst () rajta kapjuk meg az első elemet, és nem futunk végtelen ciklusba.

Kotlinban a gyűjtemények inkább lelkesek, mint lusták. A kivétel itt az Sorrend, amely lustán értékel.

Ez egy fontos megkülönböztetés, amelyet meg kell jegyezni, mivel az alábbi példa azt mutatja:

val eredmény = listOf (1, 2, 3, 4, 5) .térkép {n -> n * n} .szűrő {n -> n <10} .első ()

Ennek Kotlin-verziója ötöt fog előadni térkép() műveletek, öt szűrő() műveleteket, majd vonja ki az első értéket. A Java 8 verzió csak egyet fog végrehajtani térkép() és egy szűrő() mert az utolsó művelet szempontjából nem kell több.

A Kotlin összes gyűjteménye lusta sorrenddé alakítható a asSequence () módszer.

Használva Sorrend a helyett Lista a fenti példában ugyanannyi műveletet hajt végre, mint a Java 8-ban.

4. Java 8 Folyam Tevékenységek

Java 8-ban, Folyam műveletek két kategóriára oszthatók:

  • köztes és
  • terminál

A köztes műveletek lényegében átalakítanak egyet Folyam lustán egy másikba - például a Folyam az összes egész számból a-ba Folyam az összes páros egészéből.

A terminálopciók az utolsó lépés Folyam módszerláncot, és kiváltja a tényleges feldolgozást.

Kotlinban nincs ilyen megkülönböztetés. Helyette, ezek mind csak olyan funkciók, amelyek a gyűjteményt bemenetnek tekintik, és új kimenetet hoznak létre.

Ne feledje, hogy ha lelkes gyűjteményt használunk Kotlinban, akkor ezeket a műveleteket azonnal kiértékelik, ami meglepő lehet a Java-val összehasonlítva. Ha lustára van szükségünk, ne felejtsen el áttérni a-ra Sorrend első.

4.1. Közbenső műveletek

A Java 8 Streams API szinte összes közbenső műveletének ekvivalense van Kotlinban. Ezek azonban nem köztes műveletek - kivéve a Sorrend osztály - mivel a bemeneti gyűjtemény feldolgozásából teljesen lakott gyűjtemények származnak.

Ezen műveletek közül több is működik, amelyek pontosan ugyanúgy működnek - szűrő(), térkép(), flatMap (), különböző() és rendezve () - és némelyik csak ugyanazon néven működik - határ() most vesz, és kihagy () most csepp(). Például:

val oddSquared = listOf (1, 2, 3, 4, 5). szűrő {n -> n% 2 == 1} // 1, 3, 5 .térkép {n -> n * n} // 1, 9 , 25. Csepp (1) // 9, 25. Felvétel (1) // 9

Ez az egyszeri „9” - 3² értéket adja vissza.

Ezen műveletek némelyikének van egy további verziója is - a szó utótagja "Nak nek" - amely egy adott gyűjtemény helyett egy új gyűjtemény előállítása helyett kimeneti.

Ez hasznos lehet több bemeneti gyűjtemény feldolgozásához ugyanabba a kimeneti gyűjteménybe, például:

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

Ez beilleszti a „2” és „4” értékeket a „target” listába.

Az egyetlen olyan művelet, amelynek általában nincs közvetlen helyettesítése, az kandikál() - a Java 8-ban használják a Folyam egy feldolgozóvezeték közepén az áramlás megszakítása nélkül.

Ha lustát használunk Sorrend egy lelkes gyűjtemény helyett van egy mindegyiken() funkció, amely közvetlenül helyettesíti a kandikál funkció. Ez azonban csak ezen az egy osztályon létezik, és ezért tisztában kell lennünk azzal, hogy melyik típust használjuk a működéséhez.

Van néhány további variáció a szokásos közbenső műveleteknél, amelyek megkönnyítik az életet. Például a szűrő műveletnek további verziói vannak filterNotNull (), filterIsInstance (), filterNot () és filterIndexed ().

Például:

listOf (1, 2, 3, 4, 5) .map {n -> n * (n + 1) / 2} .mapIndexed {(i, n) -> "Háromszög $ i: $ n"}

Ez előállítja az első öt háromszög számot, „Háromszög szám 3: 6” formában

Egy másik fontos különbség abban, ahogyan flatMap működés működik. A Java 8 alkalmazásban erre a műveletre van szükség a Folyam például Kotlinban bármilyen gyűjteményt visszaadhat. Ez megkönnyíti a munkát.

Például:

val betűk = listOf ("Ez", "Is", "An", "Példa") .flatMap {w -> w.toCharArray ()} // Listát állít elő. szűrő {c -> Character.isUpperCase (c) }

A Java 8-ban a második sort be kellene csomagolni Arrays.toStream () hogy ez működjön.

4.2. Terminálműveletek

A Java 8 Streams API-ból származó összes szabványos terminálművelet közvetlen cserével rendelkezik Kotlinban, az egyetlen kivételével gyűjt.

Néhányuknak különböző neve van:

  • anyMatch () ->Bármi()
  • allMatch () ->minden()
  • noneMatch () ->egyik sem()

Néhányuknak további variációi vannak ahhoz, hogy működjenek együtt Kotlin különbségeivel - vannak első() és firstOrNull (), hol első dob, ha a gyűjtemény üres, de másként nem nullázható típust ad vissza.

Az érdekes eset az gyűjt. A Java 8 ezt használja az összes összegyűjtésére Folyam egyes gyűjtemények elemei egy megadott stratégia segítségével.

Ez lehetővé teszi egy önkényes eljárást Gyűjtő biztosítandó, amelyet a gyűjtemény minden elemével ellátunk, és valamiféle kimenetet fog produkálni. Ezeket a Gyűjtők segítő osztály, de szükség esetén megírhatjuk a sajátunkat.

Kotlinban szinte az összes szabványos gyűjtő közvetlen cseréje létezik, amely közvetlenül a gyűjtőobjektum tagjaként kapható - nincs szükség további lépésre a kollektor felszerelésével.

Az egyetlen kivétel itt a összegezveDupla/összefoglalóInt/összefoglalóHosszú módszerek - amelyek egyszerre eredményezik az átlagot, a számot, a min, a maximumot és az összeget. Ezek mindegyike külön-külön is előállítható - bár ennek nyilván magasabb költségei vannak.

Alternatív megoldásként kezelhetjük mindegyik hurok segítségével, és szükség esetén kézzel is kezelhetjük - nem valószínű, hogy mind az 5 értékre egyszerre lesz szükségünk, ezért csak a fontosakat kell végrehajtanunk.

5. Kiegészítő műveletek Kotlinban

Kotlin felvesz néhány további műveletet a gyűjteményekbe, amelyek a Java 8-ban nem lehetségesek, ha saját magunk nem hajtjuk végre őket.

Ezek némelyike ​​egyszerűen a szokásos műveletek kiterjesztése, a fentiek szerint. Például lehetséges az összes műveletet úgy végrehajtani, hogy az eredmény egy meglévő gyűjteményhez kerüljön, ahelyett, hogy új gyűjteményt adna vissza.

Sok esetben az is lehetséges, hogy a lambda nemcsak a kérdéses elemet, hanem az elem indexét is ellátja - a megrendelt gyűjtemények esetében, így az indexeknek van értelme.

Vannak olyan műveletek is, amelyek kifejezetten kihasználják a Kotlin semmilyen biztonságát - például; elvégezhetjük a filterNotNull () rajta Lista hogy visszatérjen a Lista, ahol minden nullát eltávolítunk.

A Kotlinban elvégezhető tényleges további műveletek, de a Java 8 adatfolyamokban nem:

  • postai irányítószám() és kibontani () - két gyűjtemény egyetlen pársorozatba történő egyesítésére, és fordítva párok gyűjteményének két gyűjteményre történő átalakítására szolgálnak
  • munkatárs - arra használatos, hogy egy gyűjteményt térképpé alakítson azzal, hogy egy lambdát biztosít a gyűjtemény minden bejegyzésének kulcs / érték párossá alakításához az eredményül kapott térképen

Például:

val számok = listOf (1, 2, 3) val szavak = listOf ("egy", "kettő", "három") számok. zip (szavak)

Ez előállítja a Lista, értékekkel 1-től 1-ig, 2-től 2-ig és 3-tól háromig.

val négyzetek = listOf (1, 2, 3, 4,5). társítson {n -> n - n * n}

Ez előállítja a Térkép, ahol a kulcsok az 1-től 5-ig terjedő számok, az értékek pedig az értékek négyzetei.

6. Összefoglalás

A Java 8-ból megszokott stream műveletek többsége Kotlinban közvetlenül használható a standard Collection osztályokban, nem kell konvertálni Folyam első.

Ezenkívül Kotlin nagyobb rugalmasságot ad ennek működéséhez, mivel több felhasználható műveletet és a meglévő műveletek több variálását adja hozzá.

Kotlin azonban alapból lelkes, nem lusta. Ez további munkákat okozhat, ha nem vagyunk óvatosak a használt gyűjtéstípusokkal kapcsolatban.