Java 8 Stream API-analogier i Kotlin

1. Introduktion

Java 8 introducerede begrebet Strømme til samlingshierarkiet. Disse giver mulighed for meget kraftig behandling af data på en meget læselig måde ved hjælp af nogle funktionelle programmeringskoncepter for at få processen til at fungere.

Vi undersøger, hvordan vi kan opnå den samme funktionalitet ved hjælp af Kotlin-idiomer. Vi vil også se på funktioner, der ikke er tilgængelige i almindelig Java.

2. Java vs. Kotlin

I Java 8 kan den nye fancy API kun bruges, når den interagerer med java.util.stream.Stream tilfælde.

Det gode er, at alle standardkollektioner - alt hvad der implementeres java.util.Collection - have en bestemt metode strøm() der kan producere en Strøm eksempel.

Det er vigtigt at huske, at Strøm er ikke en Kollektion.Det implementeres ikke java.util.Collection og det implementerer ikke nogen af ​​de normale semantikker i Samlinger i Java. Det er mere beslægtet med engangsbrug Iterator ved at det er afledt af en Kollektion og bruges til at arbejde igennem det og udføre operationer på hvert element, der ses.

I Kotlin understøtter alle samlingstyper allerede disse operationer uden først at skulle konvertere dem. En konvertering er kun nødvendig, hvis samlingens semantik er forkert - f.eks Sæt har unikke elementer, men er uordnet.

En fordel ved dette er, at der ikke er behov for en indledende konvertering fra en Kollektion ind i en Strøm, og ikke behov for en endelig konvertering fra en Strøm tilbage i en samling - ved hjælp af indsamle() opkald.

For eksempel skal vi i Java 8 skrive følgende:

someList .stream () .map () // nogle operationer .collect (Collectors.toList ());

Ækvivalenten i Kotlin er meget simpelt:

someList .map () // nogle operationer

Derudover Java 8 Strømme kan også ikke genbruges. Efter Strøm forbruges, kan den ikke bruges igen.

For eksempel fungerer følgende ikke:

Stream someIntegers = heltal.stream (); someIntegers.forEach (...); someIntegers.forEach (...); // en undtagelse

I Kotlin betyder det faktum, at det kun er normale samlinger, at dette problem aldrig opstår. Mellemstatus kan tildeles variabler og deles hurtigt, og fungerer bare som vi ville forvente.

3. dovne sekvenser

En af de vigtigste ting ved Java 8 Strømme er, at de vurderes doven. Dette betyder, at der ikke udføres mere arbejde end nødvendigt.

Dette er især nyttigt, hvis vi udfører potentielt dyre operationer på elementerne i Strøm, eller det gør det muligt at arbejde med uendelige sekvenser.

For eksempel, IntStream.genereret vil producere en potentielt uendelig Strøm af heltal. Hvis vi ringer findFirst () på det får vi det første element og ikke løber ind i en uendelig løkke.

I Kotlin er samlinger ivrige snarere end dovne. Undtagelsen her er Sekvens, som ikke dovner.

Dette er en vigtig skelnen at bemærke, som det følgende eksempel viser:

val result = listOf (1, 2, 3, 4, 5). kort {n -> n * n}. filter {n -> n <10}. første ()

Kotlin-versionen af ​​dette udfører fem kort() operationer, fem filter() operationer og derefter udtrække den første værdi. Java 8-versionen udfører kun en kort() og en filter() for set fra den sidste operation er der ikke behov for mere.

Alle samlinger i Kotlin kan konverteres til en doven sekvens ved hjælp af asSequence () metode.

Brug af en Sekvens i stedet for en Liste i ovenstående eksempel udfører det samme antal operationer som i Java 8.

4. Java 8 Strøm Operationer

I Java 8, Strøm operationer er opdelt i to kategorier:

  • mellemliggende og
  • terminal

Mellemliggende operationer konverterer i det væsentlige en Strøm ind i en anden doven - for eksempel en Strøm af alle heltal i en Strøm af alle lige heltal.

Terminalmuligheder er det sidste trin i Strøm metode kæde og udløse den faktiske behandling.

I Kotlin er der ingen sådan forskel. I stedet, alt dette er bare funktioner, der tager samlingen som input og producerer en ny output.

Bemærk, at hvis vi bruger en ivrig samling i Kotlin, evalueres disse operationer med det samme, hvilket kan være overraskende sammenlignet med Java. Hvis vi har brug for det for at være doven, skal du huske at konvertere til en Sekvens først.

4.1. Mellemliggende operationer

Næsten alle mellemliggende operationer fra Java 8 Streams API har ækvivalenter i Kotlin. Disse er dog ikke mellemliggende operationer - undtagen i tilfælde af Sekvens klasse - da de resulterer i fuldt udfyldte samlinger fra behandling af inputindsamlingen.

Ud af disse operationer er der flere, der fungerer nøjagtigt det samme - filter(), kort(), flatMap (), tydelig () og sorteret () - og nogle, der kun fungerer det samme med forskellige navne - begrænse() er nu tageog springe() er nu dråbe(). For eksempel:

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

Dette returnerer den enkelte værdi "9" - 3².

Nogle af disse operationer har også en ekstra version - efterfulgt af ordet "Til" - at output til en leveret samling i stedet for at producere en ny.

Dette kan være nyttigt til behandling af flere indgangssamlinger i den samme udgangssamling, for eksempel:

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

Dette indsætter værdierne "2" og "4" i listen "mål".

Den eneste handling, der normalt ikke har en direkte udskiftning, er kigge () - bruges i Java 8 til at gentage posterne i Strøm midt i en behandlingsrørledning uden at afbryde strømmen.

Hvis vi bruger en doven Sekvens i stedet for en ivrig samling, så er der en på hver() funktion, der erstatter direkte kigge fungere. Dette findes dog kun på denne ene klasse, og derfor skal vi være opmærksomme på, hvilken type vi bruger for at den skal fungere.

Der er også nogle yderligere variationer på standard mellemliggende operationer, der gør livet lettere. F.eks filter operation har yderligere versioner filterNotNull (), filterIsInstance (), filterNot () og filterIndexed ().

For eksempel:

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

Dette vil producere de første fem trekantede tal i form af "Trekantet nummer 3: 6"

En anden vigtig forskel er i den måde, hvorpå flatMap operation fungerer. I Java 8 kræves denne handling for at returnere a Strøm eksempel, mens det i Kotlin kan returnere enhver samlingstype. Dette gør det lettere at arbejde med.

For eksempel:

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

I Java 8 skal den anden linje pakkes ind Arrays.toStream () for at dette kan fungere.

4.2. Terminaloperationer

Alle standardterminaloperationer fra Java 8 Streams API har direkte udskiftninger i Kotlin, med den eneste undtagelse indsamle.

Et par af dem har forskellige navne:

  • anyMatch () ->nogen()
  • allMatch () ->alle()
  • noneMatch () ->ingen()

Nogle af dem har yderligere variationer at arbejde med, hvordan Kotlin har forskelle - der er først() og firstOrNull (), hvor først kaster, hvis samlingen er tom, men ellers returnerer en ikke-nullabel type.

Den interessante sag er indsamle. Java 8 bruger dette til at kunne samle alle Strøm elementer til en vis samling ved hjælp af en forudsat strategi.

Dette giver mulighed for en vilkårlig Samler skal leveres, som vil blive forsynet med hvert element i samlingen og vil producere et output af en slags. Disse bruges fra Samlere hjælperklasse, men vi kan skrive vores egne, hvis det er nødvendigt.

I Kotlin er der direkte udskiftninger af næsten alle standardsamlere, der er tilgængelige direkte som medlemmer på selve samlingsobjektet - der er ikke behov for et yderligere trin med opsamleren.

Den eneste undtagelse her er summarizingDouble/opsummerende/summarizingLong metoder - som producerer middelværdier, tæller, min, maks og sum alt på én gang. Hver af disse kan produceres individuelt - selvom det naturligvis har en højere pris.

Alternativt kan vi administrere det ved hjælp af en for-hver løkke og håndtere det i hånden, hvis det er nødvendigt - det er usandsynligt, at vi har brug for alle disse 5 værdier på samme tid, så vi behøver kun at implementere dem, der er vigtige.

5. Yderligere operationer i Kotlin

Kotlin tilføjer nogle yderligere operationer til samlinger, der ikke er mulige i Java 8 uden at implementere dem selv.

Nogle af disse er simpelthen udvidelser til standardoperationer, som beskrevet ovenfor. For eksempel er det muligt at udføre alle operationerne således, at resultatet føjes til en eksisterende samling snarere end at returnere en ny samling.

Det er også muligt i mange tilfælde at have lambda forsynet med ikke kun det pågældende element, men også elementets indeks - for samlinger, der er bestilt, og så giver indekser mening.

Der er også nogle operationer, der drager eksplicit fordel af Kotlins nul sikkerhed - for eksempel; vi kan udføre en filterNotNull () på en Liste at returnere en Liste, hvor alle nuller fjernes.

Faktiske yderligere operationer, der kan udføres i Kotlin, men ikke i Java 8 Streams, inkluderer:

  • lynlås () og pakke ud () - bruges til at kombinere to samlinger i en sekvens af par og omvendt til at konvertere en samling af par til to samlinger
  • knytte - bruges til at konvertere en samling til et kort ved at give en lambda til at konvertere hver post i samlingen til et nøgle / værdipar på det resulterende kort

For eksempel:

valnumre = listOf (1, 2, 3) valord = listOf ("et", "to", "tre") tal.zip (ord)

Dette producerer en Listemed værdier 1 til “en”, 2 til “to” og 3 til “tre”.

val kvadrater = listOf (1, 2, 3, 4,5). knytte {n -> n til n * n}

Dette producerer en Kort, hvor tasterne er tallene 1 til 5, og værdierne er kvadraterne for disse værdier.

6. Resume

De fleste af de stream-operationer, vi er vant til fra Java 8, kan bruges direkte i Kotlin på standard Collection-klasser uden behov for at konvertere til en Strøm først.

Derudover tilføjer Kotlin mere fleksibilitet til, hvordan dette fungerer, ved at tilføje flere operationer, der kan bruges og mere variation på de eksisterende operationer.

Kotlin er dog ivrig som standard ikke doven. Dette kan medføre, at der udføres yderligere arbejde, hvis vi ikke er forsigtige med de samlingstyper, der bruges.