Kotlin fra scratch Mer moro med funksjoner

Kotlin er et moderne programmeringsspråk som kompilerer til Java bytecode. Det er gratis og åpen kildekode, og lover å gjøre koding for Android enda morsommere.  

I forrige artikkel lærte du om pakker og grunnleggende funksjoner i Kotlin. Funksjoner er i hjertet av Kotlin, så i dette innlegget vil vi se nærmere på dem. Vi undersøker følgende typer funksjoner i Kotlin:

  • toppnivå funksjoner
  • lambda-uttrykk eller funksjonsbokstaver
  • anonyme funksjoner
  • lokale eller nestede funksjoner
  • infix funksjoner
  • medlemsfunksjoner

Du vil bli overrasket over alle de kule tingene du kan gjøre med funksjoner i Kotlin!

1. Toppnivåfunksjoner

Funksjoner på toppnivå er funksjoner i en Kotlin-pakke som er definert utenfor klassen, objektet eller grensesnittet. Dette betyr at de er funksjoner du ringer direkte, uten å måtte opprette noen objekter eller ringe noen klasser. 

Hvis du er en Java-koder, vet du at vi vanligvis lager verktøy statiske metoder i hjelperklasser. Disse hjelperklassene gjør egentlig ikke noe - de har ingen tilstands- eller instansmetoder, og de fungerer bare som en beholder for de statiske metodene. Et typisk eksempel er samlinger klasse i java.util pakke og dens statiske metoder. 

Toppnivåfunksjoner i Kotlin kan brukes som erstatning for de statiske verktøyene i hjelperkategorier vi kodes i Java. La oss se på hvordan du definerer en toppnivåfunksjon i Kotlin. 

pakke com.chikekotlin.projectx.utils sjekk sjekkUserStatus (): String return "online"

I koden ovenfor definerte vi en pakke com.chikekotlin.projectx.utils inne i en fil som heter UserUtils.kt og definerte også en toppnøkkelfunksjon som kalles checkUserStatus () inne i samme pakke og fil. For korthetens skyld returnerer denne svært enkle funksjonen strengen "online". 

Den neste tingen vi skal gjøre er å bruke denne funksjonen i en annen pakke eller fil.

pakke com.chikekotlin.projectx.users importere com.chikekotlin.projectx.utils.checkUserStatus if (checkUserStatus () == "online") // gjør noe

I den forrige koden importerte vi funksjonen til en annen pakke og deretter utført den! Som du kan se trenger vi ikke å opprette et objekt eller referere til en klasse for å ringe denne funksjonen.

Java interoperabilitet

Gitt at Java ikke støtter toppnivåfunksjoner, vil Kotlin-kompilatoren bak kulissene opprette en Java-klasse, og de enkelte toppnivåfunksjonene blir konvertert til statiske metoder. I vårt eget tilfelle var Java-klassen generert UserUtilsKt med en statisk metode checkUserStatus ()

/ * Java * / pakke com.chikekotlin.projectx.utils offentlig klasse UserUtilsKt offentlig statisk String checkUserStatus () return "online"; 

Dette betyr at Java-oppringere bare kan kalle metoden ved å referere til den genererte klassen, akkurat som for enhver annen statisk metode.

/ * Java * / import com.chikekotlin.projectx.utils.UserUtilsKt ... UserUtilsKt.checkUserStatus ()

Vær oppmerksom på at vi kan endre Java-klassenavnet som Kotlin-kompilatoren genererer ved å bruke @JvmName merknad.

@file: JvmName ("UserUtils") pakke com.chikekotlin.projectx.utils morsom sjekkeStatusStatus (): String return "online"

I koden ovenfor brukte vi @JvmName annotasjon og angitt et klassenavn UserUtilsfor den genererte filen. Merk også at denne merknaden er plassert i begynnelsen av Kotlin-filen, før pakkedefinisjonen. 

Det kan refereres fra Java slik:

/ * Java * / import com.chikekotlin.projectx.utils.UserUtils ... UserUtils.checkUserStatus ()

2. Lambda uttrykk

Lambda-uttrykk (eller funksjonsbokstavene) er heller ikke bundet til noen enhet som en klasse, objekt eller grensesnitt. De kan overføres som argumenter til andre funksjoner som kalles høyereordningsfunksjoner (vi diskuterer disse i neste innlegg). Et lambda-uttrykk representerer bare en blokk av en funksjon, og ved å bruke dem reduseres støyen i vår kode. 

Hvis du er en Java-koder, vet du at Java 8 og nyere gir støtte til lambda-uttrykk. For å bruke lambda-uttrykk i et prosjekt som støtter tidligere Java-versjoner som Java 7, 6 eller 5, kan vi bruke det populære Retrolambda-biblioteket. 

En av de fantastiske tingene om Kotlin er at lambda-uttrykkene støttes ut av boksen. Fordi lambda ikke støttes i Java 6 eller 7, for Kotlin å interoperere med det, oppretter Kotlin en Java-anonym klasse bak scenen. Men vær oppmerksom på at å lage et lambda-uttrykk i Kotlin er ganske annerledes enn det er i Java.

Her er kjennetegnene til et lambda-uttrykk i Kotlin:

  • Det må være omgitt av krøllete braces .
  • Den har ikke moro søkeord. 
  • Det er ingen tilgang modifikator (privat, offentlig eller beskyttet) fordi den ikke tilhører noen klasse, objekt eller grensesnitt.
  • Det har ingen funksjonsnavn. Med andre ord er det anonymt. 
  • Ingen returtype er spesifisert fordi det vil bli utledet av kompilatoren.
  • Parametrene er ikke omgitt av parenteser ()

Og dessuten kan vi tilordne et lambda-uttrykk til en variabel og deretter utføre den. 

Opprette Lambda-uttrykk

La oss nå se noen eksempler på lambda-uttrykk. I koden under opprettet vi et lambda-uttrykk uten noen parametre og tildelte det en variabel budskap. Vi kjørte da lambda-uttrykket ved å ringe budskap()

val message = println ("Hei, Kotlin er veldig kul!") melding () // "Hei, Kotlin er veldig kul!"

La oss også se hvordan å legge inn parametere i et lambda-uttrykk. 

val message = myString: String -> println (myString) melding ("Jeg elsker Kotlin") // "Jeg elsker Kotlin" melding ("Hvor langt?") // "Hvor langt?"

I koden ovenfor opprettet vi et lambda-uttrykk med parameteren myString, sammen med parameter typen string. Som du kan se, foran parameter typen, er det en pil: dette refererer til lambda kroppen. Med andre ord, separerer denne pilen parameterlisten fra lambda-kroppen. For å gjøre det mer konsistent, kan vi helt ignorere parametertypen (allerede utledet av kompilatoren). 

val message = myString -> println (myString) // vil fortsatt kompilere

For å ha flere parametere, skiller vi dem bare med komma. Og husk, vi bryter ikke parameterlisten i parentes som i Java. 

val addNumbers = number1: Int, number2: Int -> println ("Legge til $ number1 og $ number2") valresultat = number1 + number2 println ("Resultatet er $ result") addNumbers (1, 3)

Vær imidlertid oppmerksom på at hvis parametertypene ikke kan utledes, må de spesifiseres eksplisitt (som i dette eksemplet), ellers vil koden ikke kompilere.

Legge til 1 og 3 Resultatet er 4

Passerer Lambdas til Funksjoner

Vi kan sende lambda-uttrykk som parametre til funksjoner: disse kalles "høyere-order-funksjoner", fordi de er funksjoner av funksjoner. Disse funksjonene kan akseptere en lambda eller en anonym funksjon som parameter: for eksempel, siste() samlingsfunksjon. 

I koden nedenfor passerte vi i et lambda-uttrykk til siste() funksjon. (Hvis du vil ha en oppfriskning på samlinger i Kotlin, besøk den tredje opplæringen i denne serien) Som navnet sier, returnerer det siste elementet i listen.  siste() aksepterer et lambda-uttrykk som en parameter, og dette uttrykket tar i sin tur ett argument av typen string. Funksjonsorganet fungerer som et predikat for å søke i en delmengde av elementer i samlingen. Det betyr at lambda-uttrykket vil bestemme hvilke elementer i samlingen som skal vurderes når man ser etter den siste.

val strengliste: Liste = listOf ("in", "the", "club") utskrift (stringList.last ()) // vil skrive ut "club" utskrift (stringList.last (s: String -> s.length == 3) ) // vil skrive ut "the"

La oss se hvordan du gjør den siste kodelinjen over mer lesbar.

stringList.last s: String -> s.length == 3 // vil også kompilere og skrive ut "the" 

Kotlin-kompilatoren gjør det mulig for oss å fjerne funksjonal parentes hvis det siste argumentet i funksjonen er et lambda-uttrykk. Som du kan observere i koden ovenfor, fikk vi lov til å gjøre dette fordi det siste og eneste argumentet passerte til siste() funksjon er et lambda-uttrykk. 

Videre kan vi gjøre det mer konsistent ved å fjerne parametertypen.

stringList.last s -> s.length == 3 // vil også kompilere utskrift "the" 

Vi trenger ikke spesifisere parameter typen eksplisitt, fordi parameter typen er alltid den samme som samling element type. I koden ovenfor ringer vi siste på en liste samling av string objekter, så Kotlin-kompilatoren er smart nok til å vite at parameteren også vil være a string type. 

De den Argumentnavn

Vi kan til og med forenkle lambda-uttrykket ytterligere på nytt ved å erstatte lambda-uttrykksargumentet med det automatisk genererte standardargumentnavnet den.

stringList.last it.length == 3

De den Argumentnavn ble automatisk generert fordi siste kan akseptere et lambda-uttrykk eller en anonym funksjon (vi kommer til det kort) med bare ett argument, og dens type kan utledes av kompilatoren.  

Lokal retur i Lambda-uttrykk

La oss starte med et eksempel. I koden nedenfor passerer vi et lambda-uttrykk til for hver() funksjon påkalt på intList samling. Denne funksjonen løper gjennom samlingen og utfører lambda på hvert element i listen. Hvis et element er delbart med 2, vil det stoppe og returnere fra lambda. 

morsom surroundingFunction () val intList = listOf (1, 2, 3, 4, 5) intList.forEach if (it% 2 == 0) return println ("End of surroundingFunction ()") surroundingFunction ) // ingenting skjedde

Å kjøre koden ovenfor har kanskje ikke gitt deg det resultatet du kanskje har forventet. Dette skyldes at returmeldingen ikke kommer tilbake fra lambdaen, men i stedet fra den inneholdende funksjonen surroundingFunction ()! Dette betyr at den siste kodenetningen i surroundingFunction () vil ikke utføre. 

// ... println ("End of surroundingFunction ()") // Dette vil ikke utføre // ... 

For å fikse dette problemet må vi fortelle det eksplisitt hvilken funksjon å returnere fra ved å bruke en etikett eller navnemerke. 

morsom surroundingFunction () val intList = listOf (1, 2, 3, 4, 5) intList.forEach hvis (det% 2 == 0) return @ forEach println ("End of surroundingFunction ()") / / Nå vil det utføre surroundingFunction () // print "End of surroundingFunction ()"

I den oppdaterte koden ovenfor angav vi standardmerket @for hver umiddelbart etter komme tilbake søkeord inne i lambda. Vi har nå instruert kompilatoren til å returnere fra lambda i stedet for den inneholdende funksjonen surroundingFunction (). Nå den siste uttalelsen av surroundingFunction () vil utføre. 

Merk at vi også kan definere vår egen etikett eller navnemerke. 

 // ... intList.forEk myLabel @ if (it% 2 == 0) return @ myLabel // ... 

I koden ovenfor definerte vi vår egendefinerte etikett som heter mylabel @ og deretter spesifisert den for komme tilbake søkeord. De @for hver etikett generert av kompilatoren for for hver funksjonen er ikke lenger tilgjengelig fordi vi har definert vår egen. 

Men du vil snart se hvordan dette lokale returproblemet kan løses uten etiketter når vi diskuterer anonyme funksjoner i Kotlin snart.

3. Medlemsfunksjoner

Denne typen funksjon er definert i en klasse, objekt eller grensesnitt. Ved hjelp av medlemsfunksjoner kan vi modulere våre programmer videre. La oss nå se hvordan du oppretter en medlemsfunksjon.

klassen sirkel morsom beregningArea (radius: dobbelt): Dobbelt krever (radius> 0, "Radius må være større enn 0") returner Math.PI * Math.pow (radius, 2.0)

Denne kodestykket viser en klasse Sirkel (vi diskuterer Kotlin-klasser i senere innlegg) som har en medlemsfunksjon calculateArea (). Denne funksjonen tar en parameter radius å beregne området av en sirkel.

For å påberope en medlemsfunksjon bruker vi navnet på den inneholdende klassen eller objektet forekomsten med en prikk, etterfulgt av funksjonsnavnet, og sender eventuelle argumenter om nødvendig.

val sirkel = Sirkel () utskrift (circle.calculateArea (4.5)) // vil skrive ut "63.61725123519331"

4. Anonyme funksjoner

En anonym funksjon er en annen måte å definere en blokk med kode som kan overføres til en funksjon. Det er ikke bundet til noen identifikator. Her er kjennetegnene til en anonym funksjon i Kotlin:

  • har ikke noe navn
  • er opprettet med moro søkeord
  • inneholder en funksjonsdel
val strengliste: Liste = listOf ("in", "the", "club") print (stringList.last it.length == 3) // vil skrive ut "the"

Fordi vi passerte en lambda til siste() funksjonen ovenfor, kan vi ikke være eksplisitte om returtypen. For å være eksplisitt om returtypen, må vi bruke en anonym funksjon i stedet.

val strLenThree = stringList.last (morsom (streng): Boolsk return string.length == 3) print (strLenThree) // vil skrive ut "the"

I ovennevnte kode har vi erstattet lambda-uttrykket med en anonym funksjon fordi vi ønsker å være eksplisitt om returtypen. 

Mot slutten av lambda-delen i denne opplæringen brukte vi en etikett for å spesifisere hvilken funksjon som skal returneres fra. Bruk en anonym funksjon i stedet for en lambda inne i for hver() funksjonen løser dette problemet enklere. Returtrykket returnerer fra den anonyme funksjonen og ikke fra den omliggende, som i vårt tilfelle er surroundingFunction ().

morsomt rundtFunksjon () val intList = listOf (1, 2, 3, 4, 5) intList.forEach (morsomt (tall) hvis (tall% 2 == 0) return) println ("End of surroundingFunction ) ") // setning utført surroundingFunction () // vil skrive ut" End of surroundingFunction () "

5. Lokale eller nestede funksjoner

For å ta programmodularisering videre, gir Kotlin oss lokale funksjoner - også kjent som nestede funksjoner. En lokal funksjon er en funksjon som er deklarert i en annen funksjon. 

morsomt printCircumferenceAndArea (radius: Double): Enhet morsom calCircumference (radius: Double): Dobbel = (2 * Math.PI) * radiusval circumference = "% .2f" .format (calCircumference (radius)) Double): Double = (Math.PI) * Math.pow (radius, 2.0) val area = "% .2f" .format (calArea (radius)) print ("Sirkelomkretsen på $ radiusradius er $ omkrets og område er $ område ") printCircumferenceAndArea (3.0) // Sirkelomkretsen på 3,0 radius er 18,85 og området er 28,27

Som du kan observere i kodestykket ovenfor, har vi to enkeltlinjefunksjoner: calCircumference () og calArea () nestet inne i printCircumferenceAndAread () funksjon. De nestede funksjonene kan bare kalles fra innsiden av funksjonen og ikke utenfor. Igjen, bruk av nestede funksjoner gjør vårt program mer modulært og ryddig. 

Vi kan gjøre våre lokale funksjoner mer konsise ved ikke å eksplisitt sende parametere til dem. Dette er mulig fordi lokale funksjoner har tilgang til alle parametere og variabler av omsluttingsfunksjonen. La oss se det nå i aktion:

morsom printCircumferenceAndArea (radius: Double): Enhet morsom calCircumference (): Dobbel = (2 * Math.PI) * radius val circumference = "% .2f" .format (calCircumference ()) morsom calArea (): Double = .PI) * Math.pow (radius, 2,0) val område = "% .2f" .format (calArea ()) // ...

Som du kan se, ser denne oppdaterte koden mer lesbar og reduserer støyen vi hadde før. Selv om omsluttingsfunksjonen i dette eksemplet er liten, i en større omslutningsfunksjon som kan brytes ned i mindre nestede funksjoner, kan denne funksjonen virkelig komme til nytte. 

6. Infix Funksjoner

De infiks notat gjør det mulig for oss å enkelt ringe en en-medlems funksjon eller utvidelsesfunksjon. I tillegg til at en funksjon er ett argument, må du også definere funksjonen ved hjelp av infiks modifier. For å opprette en infix-funksjon er to parametre involvert. Den første parameteren er målobjektet, mens den andre parameteren bare er en enkelt parameter som sendes til funksjonen. 

Opprette en infix-medlemsfunksjon

La oss se på hvordan du lager en infix-funksjon i en klasse. I kodeseksempelet nedenfor opprettet vi en Student klasse med en mutable kotlinScore eksempel felt. Vi opprettet en infix-funksjon ved å bruke infiks modifikator før moro søkeord. Som du ser nedenfor, opprettet vi en infix-funksjon addKotlinScore () som tar en score og legger til kotlinScore forekomstfelt. 

klasse Student var kotlinScore = 0.0 infix moro addKotlinScore (poengsum: Dobbel): Enhet this.kotlinScore = kotlinScore + score

Ringer en infix-funksjon

La oss også se hvordan du bruker den infix-funksjonen vi har opprettet. For å ringe en infix-funksjon i Kotlin trenger vi ikke å bruke punktnotasjonen, og vi trenger ikke å vikle parameteren med parenteser. 

val student = Student () student addKotlinScore 95,00 print (student.kotlinScore) // vil skrive ut "95.0"

I koden ovenfor kalte vi infix-funksjonen, målobjektet er student, og den doble 95.00 er parameteren gått til funksjonen. 

Ved hjelp av infix-funksjoner kan vi gjøre koden mer uttrykksfulle og klarere enn den vanlige stilen. Dette er høyt verdsatt når du skriver enhetstester i Kotlin (vi diskuterer testing i Kotlin i et fremtidig innlegg).

"Chike" burde starte med ("ch") myList skal inneholde (myElement) "Chike" should haveLength (5) myMap skal haKey (myKey) 

De til Infix-funksjon

I Kotlin kan vi lage en Par forekommer mer kortfattet ved å bruke til infix funksjon i stedet for Par konstruktør. (Bak scenen, til skaper også en Par eksempel.) Merk at til funksjon er også en utvidelsesfunksjon (vi diskuterer disse i neste innlegg).

offentlig infix moro  A.to (at: B): Par = Par (dette, det)

La oss nå sammenligne opprettelsen av a Par eksempel ved bruk av begge til infix funksjon og direkte bruk av Par konstruktør, som utfører samme operasjon, og se hvilken som er bedre.

val nigeriaCallingCodePair = 234 til "Nigeria" val nigeriaCallingCodePair2 = Par (234, "Nigeria") // Samme som ovenfor

Som du kan se i koden ovenfor, bruker du til infix-funksjonen er mer konsis enn direkte ved bruk av Par konstruktør for å lage en Par forekomst. Husk at du bruker til infix funksjon, 234 er målobjektet og string "Nigeria" er parameteren overført til funksjonen. Legg merke til at vi også kan gjøre dette for å lage en Par type:

val nigeriaCallingCodePair3 = 234.to ("Nigeria") // samme som å bruke 234 til "Nigeria"

I Ranges and Collections posten opprettet vi en kartsamling i Kotlin ved å gi den en liste over par - den første verdien er nøkkelen, og den andre verdien. La oss også sammenligne opprettelsen av et kart ved å bruke begge til infix funksjon og Par konstruktør for å lage de enkelte parene.

val callingCodesMap: Kart = mapOf (234 til "Nigeria", 1 til "USA", 233 til "Ghana")

I koden ovenfor opprettet vi en kommaseparert liste over Par typer som bruker til infix funksjon og sendt dem til mapOf () funksjon. Vi kan også lage det samme kartet ved å bruke Par konstruktør for hvert par.

val callingCodesPairMap: Kart = mapOf (Par (234, "Nigeria"), Par (1, "USA"), Par (233, "Ghana"))

Som du kan se igjen, stikker du med til infix-funksjonen har mindre lyd enn å bruke Par konstruktør. 

Konklusjon

I denne opplæringen lærte du om noen av de kule tingene du kan gjøre med funksjoner i Kotlin. Vi dekket:

  • toppnivå funksjoner
  • lambda-uttrykk eller funksjonsbokstaver
  • medlemsfunksjoner
  • anonyme funksjoner
  • lokale eller nestede funksjoner
  • infix funksjoner

Men det er ikke alt! Det er fortsatt mer å lære om funksjoner i Kotlin. Så i neste innlegg lærer du noen avanserte bruksområder av funksjoner, for eksempel utvidelsesfunksjoner, høyere rekkefølgefunksjoner og nedleggelser. Ser deg snart!

For å lære mer om Kotlin-språket, anbefaler jeg at du besøker Kotlin-dokumentasjonen. Eller sjekk ut noen av våre andre Android-apputviklingsposter her på Envato Tuts+!