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
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.