Kotlin From Scratch meer plezier met functies

Kotlin is een moderne programmeertaal die compileert met Java bytecode. Het is gratis en open source en belooft om het coderen voor Android nog leuker te maken.  

In het vorige artikel leerde je over pakketten en basisfuncties in Kotlin. Functies vormen de kern van Kotlin, dus in deze post zullen we beter naar ze kijken. We zullen de volgende soorten functies in Kotlin verkennen:

  • functies op het hoogste niveau
  • lambda-expressies of functie-literalen
  • anonieme functies
  • lokale of geneste functies
  • infix-functies
  • lid functies

Je zult versteld staan ​​van alle leuke dingen die je kunt doen met functies in Kotlin!

1. Functies op het hoogste niveau

Functies op het hoogste niveau zijn functies in een Kotlin-pakket die buiten een klasse, object of interface zijn gedefinieerd. Dit betekent dat het functies zijn die u rechtstreeks belt, zonder dat u een object hoeft te maken of een klasse hoeft te bellen. 

Als u een Java-coder bent, weet u dat we meestal hulpprogramma-statische methoden maken in helperklassen. Deze helperklassen doen echt niets - ze hebben geen enkele toestands- of instantiemethode, en ze fungeren gewoon als een container voor de statische methoden. Een typisch voorbeeld is de collecties klasse in de java.util pakket en de statische methoden. 

Functies op het hoogste niveau in Kotlin kunnen worden gebruikt als vervanging voor de statische utiliteitsmethoden binnen helperklassen die we in Java coderen. Laten we eens kijken hoe we een functie op het hoogste niveau in Kotlin kunnen definiëren. 

package com.chikekotlin.projectx.utils fun checkUserStatus (): String return "online"

In de bovenstaande code hebben we een pakket gedefinieerd com.chikekotlin.projectx.utils in een bestand genaamd UserUtils.kt en definieerde ook een functie op het hoogste niveau genaamd checkUserStatus () in ditzelfde pakket en bestand. Om kortheidshalve retourneert deze zeer eenvoudige functie de tekenreeks "online". 

Het volgende dat we zullen doen, is deze nutsfunctie in een ander pakket of bestand gebruiken.

pakket com.chikekotlin.projectx.users import com.chikekotlin.projectx.utils.checkUserStatus if (checkUserStatus () == "online") // do something

In de vorige code hebben we de functie in een ander pakket geïmporteerd en vervolgens uitgevoerd! Zoals u kunt zien, hoeven we geen object te maken of naar een klasse te verwijzen om deze functie aan te roepen.

Java-interoperabiliteit

Aangezien Java geen functies op het hoogste niveau ondersteunt, maakt de Kotlin-compiler achter de schermen een Java-klasse en worden de afzonderlijke functies op het hoogste niveau geconverteerd naar statische methoden. In ons eigen geval was de gegenereerde Java-klasse dat UserUtilsKt met een statische methode checkUserStatus ()

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

Dit betekent dat Java-bellers de methode gewoon kunnen aanroepen door naar de gegenereerde klasse te verwijzen, net als voor elke andere statische methode.

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

Merk op dat we de Java-klassenaam die de Kotlin-compiler genereert kunnen veranderen door de @JvmName aantekening.

@file: JvmName ("UserUtils") pakket com.chikekotlin.projectx.utils fun checkUserStatus (): String return "online"

In de bovenstaande code hebben we het @JvmName annotatie en specificeerde een klassenaam UserUtilsvoor het gegenereerde bestand. Merk ook op dat deze annotatie aan het begin van het Kotlin-bestand wordt geplaatst, vóór de pakketdefinitie. 

Er kan op deze manier naar Java worden verwezen:

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

2. Lambda-expressies

Lambda-expressies (of functie-letterwoorden) zijn ook niet gebonden aan een entiteit zoals een klasse, object of interface. Ze kunnen worden doorgegeven als argumenten voor andere functies, hogere orde functies genoemd (we zullen deze meer bespreken in de volgende post). Een lambda-expressie vertegenwoordigt alleen het blok van een functie en het gebruik ervan vermindert de ruis in onze code. 

Als u een Java-coder bent, weet u dat Java 8 en hoger ondersteuning biedt voor lambda-expressies. Om lambda-expressies te gebruiken in een project dat eerdere Java-versies ondersteunt, zoals Java 7, 6 of 5, kunnen we de populaire Retrolambda-bibliotheek gebruiken. 

Een van de geweldige dingen over Kotlin is dat lambda-expressies uit de doos worden ondersteund. Omdat lambda niet wordt ondersteund in Java 6 of 7, maakt Kotlin voor het samenwerken met Kotlin een Java-anonieme klasse achter de schermen. Maar merk op dat het creëren van een lambda-expressie in Kotlin heel anders is dan op Java.

Hier zijn de kenmerken van een lambda-expressie in Kotlin:

  • Het moet omringd zijn door accolades .
  • Het heeft niet de pret trefwoord. 
  • Er is geen toegangsmodificator (privé, openbaar of beschermd) omdat deze niet tot een klasse, object of interface behoort.
  • Het heeft geen functienaam. Met andere woorden, het is anoniem. 
  • Er is geen retourtype opgegeven omdat dit wordt afgeleid door de compiler.
  • Parameters worden niet omringd door haakjes ()

En, wat meer is, we kunnen een lambda-expressie aan een variabele toewijzen en deze vervolgens uitvoeren. 

Lambda-expressies maken

Laten we nu enkele voorbeelden van lambda-expressies bekijken. In de onderstaande code hebben we een lambda-expressie zonder parameters gemaakt en een variabele toegewezen bericht. We hebben toen de lambda-expressie uitgevoerd door te bellen bericht()

val message = println ("Hey, Kotlin is really cool!") message () // "Hey, Kotlin is echt gaaf!"

Laten we ook kijken hoe parameters in een lambda-expressie worden opgenomen. 

val message = myString: String -> println (myString) bericht ("I love Kotlin") // "I love Kotlin" bericht ("Hoe ver?") // "Hoe ver?"

In de bovenstaande code hebben we een lambda-expressie gemaakt met de parameter MyString, samen met het parameter type Draad. Zoals je kunt zien, is er vóór het parametertype een pijl: dit verwijst naar het lambda-lichaam. Met andere woorden, deze pijl scheidt de parameterlijst van het lambda-lichaam. Om het beknopter te maken, kunnen we het parametertype volledig negeren (al afgeleid door de compiler). 

val message = myString -> println (myString) // zal nog steeds compileren

Om meerdere parameters te hebben, scheiden we ze met een komma. En onthoud, we wikkelen de parameterlijst niet tussen haakjes zoals in Java. 

val addNumbers = nummer1: Int, nummer2: Int -> println ("Toevoegen $ number1 and $ number2") val result = number1 + number2 println ("Het resultaat is $ resultaat") addNumbers (1, 3)

Merk echter op dat als de parametertypen niet kunnen worden afgeleid, ze expliciet moeten worden opgegeven (zoals in dit voorbeeld), anders wordt de code niet gecompileerd.

Het toevoegen van 1 en 3 Het resultaat is 4

Lambdas doorgeven aan functies

We kunnen lambda-expressies als parameters doorgeven aan functies: deze worden "functies van een hogere orde" genoemd, omdat ze functies van functies zijn. Dit soort functies kan een lambda of een anonieme functie als parameter accepteren: bijvoorbeeld de laatste() verzamelfunctie. 

In de onderstaande code hebben we een lambda-uitdrukking doorgegeven aan de laatste() functie. (Als u een opfriscursus wilt hebben over collecties in Kotlin, bezoekt u de derde zelfstudie in deze reeks) Zoals de naam al zegt, wordt het laatste element in de lijst geretourneerd.  laatste() accepteert een lambda-uitdrukking als een parameter en deze uitdrukking neemt op zijn beurt één type argument aan Draad. Het functielichaam ervan dient als een predikaat om te zoeken binnen een subset van elementen in de verzameling. Dat betekent dat de lambda-expressie bepaalt welke elementen van de collectie in aanmerking worden genomen bij het zoeken naar de laatste.

val stringList: lijst = listOf ("in", "the", "club") print (stringList.last ()) // zal "club" print afdrukken (stringList.last (s: String -> s.length == 3) ) // zal "de" afdrukken

Laten we eens kijken hoe we die laatste regel code hierboven leesbaarder kunnen maken.

stringList.last s: String -> s.length == 3 // zal ook "the" compileren en afdrukken 

Met de Kotlin-compiler kunnen we de functiehaakjes verwijderen als het laatste argument in de functie een lambda-uitdrukking is. Zoals je kunt zien in de bovenstaande code, mochten we dit doen omdat het laatste en enige argument werd doorgegeven aan de laatste() functie is een lambda-uitdrukking. 

Bovendien kunnen we het beknopter maken door het parametertype te verwijderen.

stringList.last s -> s.length == 3 // compileert ook print "the" 

We hoeven het parametertype niet expliciet op te geven, omdat het parametertype altijd hetzelfde is als het type verzameling-element. In de bovenstaande code bellen we laatste op een lijstcollectie van Draad objecten, dus de Kotlin-compiler is slim genoeg om te weten dat de parameter ook een is Draad type. 

De het Argumentnaam

We kunnen de lambda-expressie zelfs nog verder vereenvoudigen door het lambda-expressieargument te vervangen door de automatisch gegenereerde standaardargynaam het.

stringList.last it.length == 3

De het argumentnaam is automatisch gegenereerd omdat laatste kan een lambda-expressie of een anonieme functie accepteren (we komen daar zo snel bij) met slechts één argument, en het type kan worden afgeleid door de compiler.  

Lokaal rendement in Lambda Expressions

Laten we beginnen met een voorbeeld. In de onderstaande code geven we een lambda-uitdrukking door aan de foreach () functie opgeroepen op de intList verzameling. Deze functie doorloopt de verzameling en voert de lambda uit op elk element in de lijst. Als een element deelbaar is door 2, zal het stoppen en terugkeren van de lambda. 

fun surrounding Function () val intList = listOf (1, 2, 3, 4, 5) intList.forEach if (it% 2 == 0) return println ("End of surroundingFunction ()") surroundingFunction ( ) // er is niks gebeurd

Als u de bovenstaande code uitvoert, heeft u mogelijk niet het verwachte resultaat ontvangen. Dit komt omdat die return-instructie niet van de lambda terugkeert, maar van de bevattende functie surroundingFunction ()! Dit betekent dat de laatste codeverklaring in de surroundingFunction () zal niet uitvoeren. 

// ... println ("Einde van surroundingFunction ()") // Dit wordt niet uitgevoerd // ... 

Om dit probleem op te lossen, moeten we het expliciet aangeven met welke functie we terugkomen door een label of naamlabel te gebruiken. 

fun surrounding Function () val intList = listOf (1, 2, 3, 4, 5) intList.forEach if (it% 2 == 0) return @ forElke println ("End of surroundingFunction ()") / / Nu wordt het uitgevoerd surroundingFunction () // print "End of surroundingFunction ()"

In de bijgewerkte code hierboven hebben we de standaardtag opgegeven @forEach onmiddellijk na de terugkeer sleutelwoord binnen de lambda. We hebben nu de compiler opdracht gegeven om terug te keren van de lambda in plaats van de bevattende functie surroundingFunction (). Nu de laatste verklaring van surroundingFunction () zal uitvoeren. 

Merk op dat we ook ons ​​eigen label of naamlabel kunnen definiëren. 

 // ... intList.forYe myLabel @ if (it% 2 == 0) keer terug @ myLabel // ... 

In de bovenstaande code hebben we ons aangepaste label gedefinieerd mylabel @ en vervolgens opgegeven voor de terugkeer trefwoord. De @forEach label gegenereerd door de compiler voor de forEach functie is niet langer beschikbaar omdat we onze eigen hebben gedefinieerd. 

U zult echter snel zien hoe dit lokale retourprobleem zonder labels kan worden opgelost wanneer we binnenkort over anonieme functies in Kotlin spreken.

3. Ledenfuncties

Dit soort functies wordt gedefinieerd in een klasse, object of interface. Door lidfuncties te gebruiken, kunnen we onze programma's verder modulariseren. Laten we nu kijken hoe we een ledfunctie kunnen maken.

class Circle fun calculateArea (radius: Double): Double require (radius> 0, "Radius moet groter zijn dan 0") return Math.PI * Math.pow (radius, 2.0)

Dit codefragment geeft een klas weer Cirkel (we bespreken Kotlin-klassen in latere berichten) met een ledenfunctie calculateArea (). Deze functie neemt een parameter radius om het gebied van een cirkel te berekenen.

Om een ​​lidfunctie aan te roepen, gebruiken we de naam van de bevattende klasse of objectinstantie met een punt, gevolgd door de functienaam, waarbij eventuele argumenten worden doorgegeven.

val circle = Circle () print (circle.calculateArea (4.5)) // zal "63.61725123519331" afdrukken

4. Anonieme functies

Een anonieme functie is een andere manier om een ​​codeblok te definiëren dat aan een functie kan worden doorgegeven. Het is niet gebonden aan een identifier. Hier zijn de kenmerken van een anonieme functie in Kotlin:

  • heeft geen naam
  • is gemaakt met de pret trefwoord
  • bevat een functie-instantie
val stringList: lijst = listOf ("in", "the", "club") print (stringList.last it.length == 3) // zal "de" afdrukken

Omdat we een lambda naar de laatste() functie hierboven, kunnen we niet expliciet zijn over het retourneringstype. Om expliciet te zijn over het retourneringstype, moeten we in plaats daarvan een anonieme functie gebruiken.

val strLenThree = stringList.last (fun (string): Boolean return string.length == 3) print (strLenThree) // zal "the" afdrukken

In de bovenstaande code hebben we de lambda-expressie vervangen door een anonieme functie omdat we expliciet willen zijn over het retourneringstype. 

Naar het einde van de lambda-sectie in deze zelfstudie hebben we een label gebruikt om op te geven vanaf welke functie moet worden teruggekomen. Gebruik een anonieme functie in plaats van een lambda in de forEach () functie lost dit probleem eenvoudiger op. De retourexpressie komt terug van de anonieme functie en niet van de omringende, wat in ons geval het geval is surroundingFunction ().

fun surrounding Function () val intList = listOf (1, 2, 3, 4, 5) intList.forEach (fun (number) if (number% 2 == 0) return) println ("Einde van surroundingFunction ( ) ") // statement executed surroundingFunction () // zal" End of surroundingFunction () "afdrukken

5. Lokale of geneste functies

Om de modularisering van het programma verder te zetten, biedt Kotlin ons lokale functies-ook bekend als geneste functies. Een lokale functie is een functie die wordt gedeclareerd in een andere functie. 

fun printCircumferenceAndArea (radius: Double): Unit fun calCircumference (radius: Double): Double = (2 * Math.PI) * radius val circumference = "% .2f" .format (calCircumference (radius)) fun calArea (radius: Double): Double = (Math.PI) * Math.pow (radius, 2.0) val area = "% .2f" .format (calArea (radius)) print ("De cirkelomtrek van $ straal straal is $ omtrek en gebied is $ area ") printCircumferenceAndArea (3.0) // De cirkelomtrek van 3.0 radius is 18.85 en het gebied is 28.27

Zoals u in het bovenstaande codefragment kunt zien, hebben we twee functies met één regel: calCircumference () en calArea () genest in de printCircumferenceAndAread () functie. De geneste functies kunnen alleen worden aangeroepen vanuit de insluitende functie en niet van buiten. Nogmaals, het gebruik van geneste functies maakt ons programma modulair en opgeruimd. 

We kunnen onze lokale functies beknopter maken door niet expliciet parameters aan hen door te geven. Dit is mogelijk omdat lokale functies toegang hebben tot alle parameters en variabelen van de omsluitende functie. Laten we dat nu in actie zien:

fun printCircumferenceAndArea (radius: Double): Unit fun calCircumference (): Double = (2 * Math.PI) * radius val circumference = "% .2f" .format (calCircumference ()) fun calArea (): Double = (Math .PI) * Math.pow (radius, 2.0) val gebied = "% .2f" .format (calArea ()) // ...

Zoals u kunt zien, lijkt deze bijgewerkte code leesbaarder en vermindert de ruis die we eerder hadden. Hoewel de insluitende functie in dit voorbeeld klein is, kan deze functie in een grotere omsluitende functie die kan worden opgesplitst in kleinere geneste functies erg handig zijn. 

6. Infix-functies

De infix notatie stelt ons in staat om eenvoudig een functie of functie met één argumentlid of extensie te bellen. Naast dat een functie één argument is, moet u ook de functie definiëren met behulp van de infix modifier. Voor het maken van een infix-functie zijn twee parameters betrokken. De eerste parameter is het doelobject, terwijl de tweede parameter slechts één enkele parameter is die aan de functie wordt doorgegeven. 

Een infix-lidfunctie maken

Laten we kijken naar hoe een infix-functie in een klasse te maken. In het onderstaande codevoorbeeld hebben we een gemaakt Student klasse met een veranderlijk kotlinScore instantieveld. We hebben een infix-functie gemaakt met behulp van de infix modifier voor de pret trefwoord. Zoals je hieronder kunt zien, hebben we een infix-functie gemaakt addKotlinScore () dat een score neemt en bijdraagt ​​aan de kotlinScore exemplaar veld. 

klas Student var kotlinScore = 0.0 infix fun addKotlinScore (score: Double): Unit this.kotlinScore = kotlinScore + score

Een Infix-functie aanroepen

Laten we ook zien hoe we de infix-functie kunnen oproepen die we hebben gemaakt. Om een ​​infix-functie in Kotlin te callen, hoeven we de puntnotatie niet te gebruiken en hoeven we de parameter niet tussen haakjes in te voegen. 

val student = Student () student addKotlinScore 95.00 print (student.kotlinScore) // zal "95.0" afdrukken

In de bovenstaande code hebben we de infix-functie genoemd, het doelobject student, en het dubbele 95.00 is de parameter doorgegeven aan de functie. 

Het verstandig gebruik van infix-functies kan onze code expressiever en duidelijker maken dan de normale stijl. Dit wordt enorm op prijs gesteld bij het schrijven van unit tests in Kotlin (we zullen testen in Kotlin bespreken in een volgende post).

"Chike" zou moeten beginnenMet ("ch") myList zou moeten bevatten (myElement) "Chike" zou moeten hebbenLength (5) myMap should haveKey (myKey) 

De naar Infix-functie

In Kotlin kunnen we de oprichting van een Paar bijvoorbeeld meer beknopt door het gebruik van de naar infix-functie in plaats van de Paar constructeur. (Achter de schermen, naar maakt ook een Paar instantie.) Merk op dat de naar functie is ook een uitbreidingsfunctie (we zullen deze meer bespreken in de volgende post).

openbare infixplezier  A.to (dat: B): koppelen = Paar (dit, dat)

Laten we nu de creatie van een vergelijken Paar bijvoorbeeld met behulp van zowel de naar infix-functie en direct gebruik van de Paar constructor, die dezelfde bewerking uitvoert en ziet welke beter is.

val nigeriaCallingCodePair = 234 tot "Nigeria" val nigeriaCallingCodePair2 = Pair (234, "Nigeria") // Hetzelfde als hierboven

Zoals u in de bovenstaande code kunt zien, gebruikt u de naar infix-functie is beknopter dan direct met behulp van de Paar constructor om een ​​te maken Paar aanleg. Vergeet niet dat het gebruik van de naar infix-functie, 234 is het doelobject en het Draad "Nigeria" is de parameter die aan de functie wordt doorgegeven. Merk bovendien op dat we dit ook kunnen doen om een Paar type:

val nigeriaCallingCodePair3 = 234.to ("Nigeria") // zelfde als het gebruik van 234 tot "Nigeria"

In de reeks Bereiken en collecties hebben we een kaartverzameling in Kotlin gemaakt door deze een lijst met paren te geven, waarbij de eerste waarde de sleutel is en de tweede de waarde. Laten we ook het maken van een kaart vergelijken door beide te gebruiken naar infix-functie en de Paar constructor om de individuele paren te maken.

val callingCodesMap: Map = mapOf (234 tot "Nigeria", 1 tot "VS", 233 tot "Ghana")

In de bovenstaande code hebben we een door komma's gescheiden lijst gemaakt van Paar types met behulp van de naar infix-functie en gaf ze door aan de kaart van() functie. We kunnen dezelfde kaart ook maken door rechtstreeks met de Paar constructor voor elk paar.

val callingCodesPairMap: kaart = mapOf (Pair (234, "Nigeria"), Pair (1, "USA"), Pair (233, "Ghana"))

Zoals je kunt zien, blijf je bij de naar infix-functie heeft minder ruis dan het gebruik van de Paar bouwer. 

Conclusie

In deze tutorial hoorde je enkele van de coole dingen die je kunt doen met functies in Kotlin. We hebben betrekking op:

  • functies op het hoogste niveau
  • lambda-expressies of functie-literalen
  • lid functies
  • anonieme functies
  • lokale of geneste functies
  • infix-functies

Maar dat is niet alles! Er is nog meer te leren over functies in Kotlin. Dus in het volgende bericht leer je enkele geavanceerde functies van functies, zoals uitbreidingsfuncties, functies van hogere orde en sluitingen. Tot ziens!

Voor meer informatie over de Kotlin-taal, raad ik aan de Kotlin-documentatie te bezoeken. Of bekijk enkele van onze andere Android-apps voor app-ontwikkeling hier op Envato Tuts+!