Kotlin från scratch Mer roligt med funktioner

Kotlin är ett modernt programmeringsspråk som kompilerar till Java bytecode. Det är gratis och öppen källkod, och lovar att göra kodning för Android ännu roligare.  

I den föregående artikeln lärde du dig om paket och grundläggande funktioner i Kotlin. Funktionerna ligger i Kotlins hjärta, så i det här inlägget ser vi närmare på dem. Vi undersöker följande typer av funktioner i Kotlin:

  • högsta nivå funktioner
  • lambda-uttryck eller funktionstavlar
  • anonyma funktioner
  • lokala eller kapslade funktioner
  • infix funktioner
  • medlemsfunktioner

Du kommer att bli förvånad över alla de coola sakerna du kan göra med funktioner i Kotlin!

1. Toppnivåfunktioner

Funktioner på toppnivå är funktioner i ett Kotlin-paket som är definierade utanför någon klass, objekt eller gränssnitt. Det betyder att de är funktioner du ringer direkt, utan att behöva skapa något objekt eller ringa någon klass. 

Om du är en Java-kodare, vet du att vi vanligtvis skapar statiska metoder för användning inom hjälparklasser. Dessa hjälparklasser gör egentligen inte någonting - de har inga statliga eller instansmetoder, och de fungerar bara som en behållare för de statiska metoderna. Ett typiskt exempel är samlingar klass i java.util paket och dess statiska metoder. 

Funktioner på toppnivå i Kotlin kan användas som ersättning för de statiska verktygsmetoderna inom hjälparklasser vi kodar i Java. Låt oss se hur du definierar en toppnivåfunktion i Kotlin. 

paket com.chikekotlin.projectx.utils rolig checkUserStatus (): String returnera "online"

I koden ovan definierade vi ett paket com.chikekotlin.projectx.utils inuti en fil som heter UserUtils.kt och definierade också en toppnivån användningsfunktion som heter checkUserStatus () inuti samma paket och fil. För korthetens skull returnerar denna mycket enkla funktion strängen "online". 

Nästa sak vi ska göra är att använda den här funktionen i ett annat paket eller en fil.

paket com.chikekotlin.projectx.users importera com.chikekotlin.projectx.utils.checkUserStatus if (checkUserStatus () == "online") // gör något

I föregående kod importerade vi funktionen till ett annat paket och utförde det sedan! Som du kan se behöver vi inte skapa ett objekt eller referera till en klass för att ringa den här funktionen.

Java Interoperabilitet

Med tanke på att Java inte stöder överordnade funktioner, skapar Kotlin-kompilatorn bakom kulisserna en Java-klass, och de enskilda toppnivånsfunktionerna konverteras till statiska metoder. I vårt eget fall var Java-klassen genererad UserUtilsKt med en statisk metod checkUserStatus ()

/ * Java * / paket com.chikekotlin.projectx.utils public class UserUtilsKt public static String checkUserStatus () returnera "online"; 

Det betyder att Java-röstare enkelt kan ringa metoden genom att referera till sin genererade klass, precis som för någon annan statisk metod.

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

Observera att vi kan ändra det Java-klassnamn som Kotlin-kompilatorn genererar genom att använda @JvmName anteckning.

@file: JvmName ("UserUtils") paket com.chikekotlin.projectx.utils rolig checkUserStatus (): String returnera "online"

I koden ovan tillämpade vi @JvmName anteckning och specificerat ett klassnamn UserUtilsför den genererade filen. Observera också att denna anteckning placeras i början av Kotlin-filen, före paketdefinitionen. 

Det kan hämtas från Java så här:

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

2. Lambda-uttryck

Lambda-uttryck (eller funktionstavlar) är inte heller bundna till någon enhet, såsom en klass, ett objekt eller ett gränssnitt. De kan överföras som argument till andra funktioner som kallas högre orderfunktioner (vi diskuterar dessa mer i nästa post). Ett lambda-uttryck representerar bara ett funktionsblock, och med hjälp av dem reduceras bruset i vår kod. 

Om du är en Java-kodare vet du att Java 8 och senare ger stöd för lambda-uttryck. För att använda lambda-uttryck i ett projekt som stöder tidigare Java-versioner, som Java 7, 6 eller 5, kan vi använda det populära Retrolambda-biblioteket. 

En av de fantastiska sakerna om Kotlin är att lambda-uttryck stöds ut ur lådan. Eftersom lambda inte stöds i Java 6 eller 7, för att Kotlin ska samverka med det skapar Kotlin en Java anonym klass bakom scenen. Men observera att att skapa ett lambda-uttryck i Kotlin är ganska annorlunda än i Java.

Här är egenskaperna hos ett lambda uttryck i Kotlin:

  • Det måste vara omgivet av lockiga hängslen .
  • Det har inte roligt nyckelord. 
  • Det finns ingen åtkomstmodifierare (privat, offentlig eller skyddad) eftersom den inte hör till någon klass, objekt eller gränssnitt.
  • Det har inget funktionsnamn. Med andra ord är det anonymt. 
  • Ingen returtyp anges eftersom det kommer att härledas av kompilatorn.
  • Parametrarna är inte omgivna av parenteser ()

Och dessutom kan vi tilldela ett lambda-uttryck till en variabel och sedan utföra det. 

Skapa Lambda-uttryck

Låt oss nu se några exempel på lambda-uttryck. I koden nedan skapade vi ett lambda-uttryck utan några parametrar och tilldelade det en variabel meddelande. Vi utförde sedan lambda-uttrycket genom att ringa meddelande()

val message = println ("Hej, Kotlin är riktigt cool!") Meddelande () // "Hej, Kotlin är verkligen cool!"

Låt oss också se hur parametrar ska inkluderas i ett lambda-uttryck. 

val meddelande = myString: String -> println (myString) meddelande ("Jag älskar Kotlin") // "Jag älskar Kotlin" meddelande ("Hur långt?") // "Hur långt?"

I koden ovan skapade vi ett lambda-uttryck med parametern Mystring, tillsammans med parametertypen Sträng. Som du kan se, framför parametertypen, finns en pil: det här hänvisar till lambda-kroppen. Med andra ord skiljer den här pilen parametern från lambda-kroppen. För att göra det mer koncis kan vi helt ignorera parametertypen (som redan har beräknats av kompilatorn). 

val message = myString -> println (myString) // kommer fortfarande att kompilera

För att ha flera parametrar separerar vi dem bara med ett komma. Och kom ihåg att vi inte bryter parametern i parentes som i Java. 

val addNumbers = number1: Int, number2: Int -> println ("Lägger till $ number1 och $ number2") valresultat = number1 + number2 println ("Resultatet är $ resultat") addNumbers (1, 3)

Observera dock att om parametertyperna inte kan utläsas, måste de anges explicit (som i det här exemplet), annars kommer koden inte att kompilera.

Lägger till 1 och 3 Resultatet är 4

Passerar lambdas till funktioner

Vi kan överföra lambda-uttryck som parametrar till funktioner: dessa kallas "högre orderfunktioner", eftersom de är funktioner av funktioner. Dessa typer av funktioner kan acceptera en lambda eller en anonym funktion som parameter: till exempel, sista() insamlingsfunktion. 

I koden nedan passerade vi i ett lambda uttryck till sista() fungera. (Om du vill ha en uppdatering på samlingar i Kotlin, besök den tredje handledningen i denna serie) Som namnet säger returnerar det sista elementet i listan.  sista() accepterar ett lambda-uttryck som en parameter, och detta uttryck tar i sin tur ett argument av typen Sträng. Dess funktionskod fungerar som ett predikat för att söka inom en delmängd av element i samlingen. Det betyder att lambda-uttrycket bestämmer vilka delar av samlingen som kommer att beaktas när man letar efter den sista.

val stringList: List = listOf ("in", "the", "club") skriv ut (stringList.last ()) // kommer skriva ut "club" -utskrift (stringList.last (s: String -> s.length == 3) ) // kommer att skriva ut "the"

Låt oss se hur du gör den sista raden av kod ovanför mer läsbar.

stringList.last s: String -> s.length == 3 // kommer också att kompilera och skriva ut "the" 

Kotlin-kompilatorn tillåter oss att ta bort funktionens parentes om det sista argumentet i funktionen är ett lambda-uttryck. Som du kan observera i koden ovan, fick vi göra detta eftersom det sista och enda argumentet passerade till sista() funktion är ett lambda uttryck. 

Dessutom kan vi göra det mer konkret genom att ta bort parametertypen.

stringList.last s -> s.length == 3 // kommer också att kompilera utskrift "the" 

Vi behöver inte specificera parametertypen, eftersom parametertypen alltid är densamma som samlingselementtypen. I koden ovan ringer vi sista på en lista samling av Sträng objekt, så Kotlin-kompilatorn är smart nog att veta att parametern också kommer att bli en Sträng typ. 

De Det Argument Namn

Vi kan även förenkla lambda-uttrycket ytterligare en gång genom att ersätta lambda-expressionsargumentet med det automatiskt genererade standardargumentnamnet Det.

stringList.last it.length == 3

De Det Argumentnamnet genererades automatiskt eftersom sista kan acceptera ett lambda-uttryck eller en anonym funktion (vi kommer snart till det) med endast ett argument, och dess typ kan härledas av kompilatorn.  

Lokal retur i Lambda-uttryck

Låt oss börja med ett exempel. I koden nedan skickar vi ett lambda uttryck till för varje() funktion påkallad på intList samling. Denna funktion kommer att gå igenom samlingen och utföra lambda på varje element i listan. Om något element är delbart med 2, kommer det att stanna och återvända från lambda. 

roligt surroundingFunction () val intList = listOf (1, 2, 3, 4, 5) intList.forEach om (det% 2 == 0) return println ("End of surroundingFunction ()") surroundingFunction ) // inget hände

Om du kör ovanstående kod kanske du inte har fått det resultat du kanske hade förväntat dig. Detta beror på att returavkastningen inte kommer att återvända från lambda utan istället från den innehålla funktionen surroundingFunction ()! Detta innebär att den sista koddeklarationen i surroundingFunction () kommer inte att utföras. 

// ... println ("Slut på surroundingFunction ()") // Detta kommer inte att utföras // ... 

För att åtgärda detta problem måste vi uttryckligen ange vilken funktion som ska återvända från med hjälp av en etikett eller namnetikett. 

roligt surroundingFunction () val intList = listOf (1, 2, 3, 4, 5) intList.forEach if (it% 2 == 0) return @ forEach println ("End of surroundingFunction ()") / / Nu kommer det att utföras surroundingFunction () // print "Slut på surroundingFunction ()"

I den uppdaterade koden ovan angav vi standardtaggen @för varje strax efter lämna tillbaka nyckelord inuti lambda. Vi har nu instruerat kompilatorn att återvända från lambda istället för den innehållande funktionen surroundingFunction (). Nu det sista uttalandet av surroundingFunction () kommer att exekvera. 

Observera att vi också kan definiera vår egen etikett eller namnetikett. 

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

I koden ovan definierade vi vår anpassade etikett som heter myLabel @ och specificerade sedan den för lämna tillbaka nyckelord. De @för varje etikett som genereras av kompilatorn för för varje funktionen är inte längre tillgänglig eftersom vi har definierat vår egen. 

Men du kommer snart att se hur det här lokala returproblemet kan lösas utan etiketter när vi diskuterar anonyma funktioner i Kotlin inom kort.

3. Medlemsfunktioner

Denna typ av funktion definieras inuti en klass, ett objekt eller ett gränssnitt. Genom att använda medlemsfunktioner kan vi modulera våra program vidare. Låt oss nu se hur du skapar en medlemsfunktion.

klassrad cirkel kul calculateArea (rad: Dubbel): Dubbel kräver (radien> 0, "Radien måste vara större än 0") returnera Math.PI * Math.pow (radie, 2.0)

Denna kodbit innehåller en klass Cirkel (vi diskuterar Kotlin-klasser i senare inlägg) som har en medlemsfunktion calculateArea (). Denna funktion tar en parameter radie för att beräkna området för en cirkel.

För att åberopa en medlemsfunktion använder vi namnet på den innehållande klass- eller objektexemplet med en punkt, följt av funktionsnamnet, som överför eventuella argument om det behövs.

val cirkel = Circle () print (circle.calculateArea (4.5)) // kommer att skriva ut "63.61725123519331"

4. Anonyma funktioner

En anonym funktion är ett annat sätt att definiera ett block av kod som kan överföras till en funktion. Det är inte bundet till någon identifierare. Här är egenskaperna hos en anonym funktion i Kotlin:

  • har inget namn
  • skapas med roligt nyckelord
  • innehåller en funktionskropp
val stringList: List = listOf ("in", "the", "club") skriv ut (stringList.last it.length == 3) // kommer skriva ut "the"

Eftersom vi passerade en lambda till sista() funktion ovan kan vi inte vara tydliga om returtypen. För att vara explicit om returtypen behöver vi istället använda en anonym funktion.

val strLenThree = stringList.last (rolig (sträng): Boolean return string.length == 3) print (strLenThree) // kommer att skriva ut "the"

I ovanstående kod har vi ersatt lambda-uttrycket med en anonym funktion eftersom vi vill vara tydliga om returtypen. 

Mot slutet av lambdasektionen i denna handledning använde vi en etikett för att ange vilken funktion som ska återvända från. Använda en anonym funktion istället för en lambda inuti för varje() funktionen löser detta problem helt enkelt. Returuttrycket returnerar från den anonyma funktionen och inte från den omgivande, som i vårt fall är surroundingFunction ().

roligt surroundingFunction () val intList = listOf (1, 2, 3, 4, 5) intList.forEach (roligt (nummer) om (nummer% 2 == 0) return) println ("Slut på SurroundFunction ) ") // uttalande utfört surroundingFunction () // kommer att skriva ut" End of surroundingFunction () "

5. Lokala eller kapslade funktioner

För att ta vidare programmodulering ger Kotlin oss lokala funktioner, även kända som kapslade funktioner. En lokal funktion är en funktion som deklareras i en annan funktion. 

rolig radCircumferenceAndArea (rad: Dubbel): Enhet rolig calCircumference (radie: Dubbel): Dubbel = (2 * Math.PI) * Radius valomkrets = "% .2f" .format (calCircumference Dubbla): Dubbel = (Math.PI) * Math.pow (radie, 2.0) val area = "% .2f" .format (calArea (radien)) print ("Cirkelomkretsen av $ radiusradie är $ omkrets och område är $ area ") printCircumferenceAndArea (3.0) // Cirkelomkretsen av 3,0 radie är 18,85 och området är 28,27

Som du kan observera i kodfältet ovan har vi två enradiga funktioner: calCircumference () och calArea () nestad inuti printCircumferenceAndAread () fungera. De kapslade funktionerna kan bara ringas från inbyggnadsfunktionen och inte utanför. Återigen gör användningen av kapslade funktioner vårt program mer modulärt och städat. 

Vi kan göra våra lokala funktioner mer koncisa genom att inte explicit överföra parametrar till dem. Detta är möjligt eftersom lokala funktioner har tillgång till alla parametrar och variabler i omslutningsfunktionen. Låt oss se det nu i aktion:

rolig radCircumferenceAndArea (rad: Dubbel): Enhet roligt calCircumference (): Dubbel = (2 * Math.PI) * Radius valomkrets = "% .2f" .format (calCircumference ()) roligt calArea (): Double = .PI) * Math.pow (radien, 2.0) valområde = "% .2f" .format (calArea ()) // ...

Som du kan se, ser den här uppdaterade koden mer läsbar och minskar bullret som vi hade tidigare. Fastän omslutningsfunktionen i det här exemplet är liten är det i en större omslutningsfunktion som kan brytas ned i mindre kapslade funktioner, den här funktionen kan verkligen vara till nytta. 

6. Infix Funktioner

De infix notering tillåter oss att enkelt ringa en en-medlems funktion eller förlängningsfunktion. Förutom att en funktion är ett argument måste du också definiera funktionen med hjälp av infix modifieringsmedel. För att skapa en infix-funktion är två parametrar inblandade. Den första parametern är målobjektet, medan den andra parametern bara är en enda parameter som skickas till funktionen. 

Skapa en Infix-medlemsfunktion

Låt oss titta på hur man skapar en infixfunktion i en klass. I kodexemplet nedan skapade vi en Studerande klass med en mutable kotlinScore exempel fältet. Vi skapade en infix-funktion genom att använda infix modifierare före roligt nyckelord. Som du kan se nedan skapade vi en infix-funktion addKotlinScore () som tar poäng och lägger till kotlinScore instansfält. 

klass Student var kotlinScore = 0.0 infix kul addKotlinScore (poäng: Double): Enhet this.kotlinScore = kotlinScore + score

Ringa en infixfunktion

Låt oss också se hur man använder den infix-funktion som vi skapat. För att ringa en infix-funktion i Kotlin behöver vi inte använda pricknotationen, och vi behöver inte vikla parametern med parentes. 

val student = Student () student addKotlinScore 95.00 print (student.kotlinScore) // kommer att skriva ut "95.0"

I koden ovan kallade vi infix-funktionen, målobjektet är studerande, och den dubbla 95,00 är parametern överförd till funktionen. 

Genom att använda infixfunktionerna kan vi göra vår kod mer uttrycklig och tydligare än den vanliga stilen. Detta uppskattas mycket när man skriver enhetsprov i Kotlin (vi diskuterar testning i Kotlin i ett framtida inlägg).

"Chike" bör starta med ("ch") myList bör innehålla (myElement) "Chike" ska haLength (5) myMap ska haKey (myKey) 

De till Infix-funktionen

I Kotlin kan vi skapa skapandet av a Par Exempel mer succinkt genom att använda till infix funktion istället för Par konstruktör. (Bakom kulisserna, till skapar också en Par exempel.) Observera att till funktionen är också en förlängningsfunktion (vi diskuterar dessa mer i nästa post).

offentlig infix kul  A.to (det: B): Par = Par (det här)

Låt oss nu jämföra skapandet av a Par exempel med både till infix funktion och direkt använda Par konstruktör, som utför samma operation, och se vilken som är bättre.

val nigeriaCallingCodePair = 234 till "Nigeria" val nigeriaCallingCodePair2 = Par (234, "Nigeria") // Samma som ovan

Som du kan se i koden ovan, använder du till infixfunktionen är mer kortfattad än att direkt använda Par konstruktör för att skapa en Par exempel. Kom ihåg att du använder till infix-funktionen, 234 är målobjektet och Sträng "Nigeria" är parametern som skickas till funktionen. Observera dessutom att vi också kan göra detta för att skapa en Par typ:

val nigeriaCallingCodePair3 = 234.to ("Nigeria") // samma som att använda 234 till "Nigeria"

I Ranges and Collections-posten skapade vi en kartsamling i Kotlin genom att ge den en lista med par - det första värdet är nyckeln och det andra värdet. Låt oss också jämföra skapandet av en karta med hjälp av både till infix funktion och Par konstruktör för att skapa de enskilda paren.

val callingCodesMap: Karta = mapOf (234 till "Nigeria", 1 till "USA", 233 till "Ghana")

I koden ovan skapade vi en kommaseparerad lista över Par typer som använder till infix funktion och skickade dem till karta över() fungera. Vi kan också skapa samma karta genom att direkt använda Par konstruktör för varje par.

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

Som du kan se igen klibbar du med till infix-funktionen har mindre ljud än att använda Par konstruktör. 

Slutsats

I denna handledning lärde du dig om några av de coola saker du kan göra med funktioner i Kotlin. Vi täckte:

  • högsta nivå funktioner
  • lambda-uttryck eller funktionstavlar
  • medlemsfunktioner
  • anonyma funktioner
  • lokala eller kapslade funktioner
  • infix funktioner

Men det är inte allt! Det finns fortfarande mer att lära sig om funktioner i Kotlin. Så i nästa inlägg lär du dig några avancerade användningsområden av funktioner, till exempel utökningsfunktioner, högre orderfunktioner och stängningar. Ses snart!

För att lära mig mer om Kotlins språket rekommenderar jag att du besöker Kotlins dokumentation. Eller kolla in några av våra andra Android App-utvecklingsposter här på Envato Tuts+!