Lambda-uitdrukkingen in Kotlin

1. Overzicht

In dit artikel gaan we Lambdas in de Kotlin-taal verkennen. Houd er rekening mee dat lambda's niet uniek zijn voor Kotlin en al vele jaren in veel andere talen bestaan.

Lambdas-expressies zijn in wezen anonieme functies die we als waarden kunnen behandelen - we kunnen ze bijvoorbeeld doorgeven als argumenten aan methoden, ze retourneren of iets anders doen dat we zouden kunnen doen met een normaal object.

2. Definiëren van een Lambda

Zoals we zullen zien, lijken Kotlin Lambdas erg op Java Lambdas. Hier vindt u meer informatie over het werken met Java Lambdas en enkele praktische tips.

Om een ​​lambda te definiëren, moeten we ons aan de syntaxis houden:

val lambdaName: Type = {argumentList -> codeBody}

Het enige deel van een lambda dat niet optioneel is, is de codeBody.

De lijst met argumenten kan worden overgeslagen bij het definiëren van maximaal één argument en de Type kan vaak worden afgeleid door de Kotlin-compiler. We hebben ook niet altijd een variabele nodig, de lambda kan direct worden doorgegeven als een methode-argument.

Het type van het laatste commando binnen een lambda-blok is het geretourneerde type.

2.1. Typ inferentie

Met de type-inferentie van Kotlin kan het type lambda worden geëvalueerd door de compiler.

Het schrijven van een lambda die het kwadraat van een getal oplevert, zou zo zijn geschreven als:

val kwadraat = {getal: Int -> getal * getal} val negen = kwadraat (3)

Kotlin zal het bovenstaande voorbeeld evalueren als een functie waarvoor er één nodig is Int en retourneert een Int:(Int) -> Int

Als we een lambda willen maken die zijn enkele argumentgetallen met 100 vermenigvuldigt, retourneert die waarde dan als een Draad:

val magnitude100String = {input: Int -> val magnitude = input * 100 magnitude.toString ()} 

Kotlin zal begrijpen dat deze lambda van het type is (Int) -> String.

2.2. Typ verklaring

Af en toe kan Kotlin onze typen niet afleiden en moeten we het type expliciet aangeven voor onze lambda; net zoals we kunnen met elk ander type.

Het patroon is input -> outputAls de code echter geen waarde retourneert, gebruiken we het type Eenheid:

val dat: Int -> Int = {drie -> drie}
val meer: ​​(String, Int) -> String = {str, int -> str + int}
val noReturn: Int -> Unit = {num -> println (num)}

We kunnen lambda's gebruiken als klasse-extensies:

val another: String. (Int) -> String = {this + it}

Het patroon dat we hier gebruiken is iets anders dan de andere lambda's die we hebben gedefinieerd. Onze haakjes bevatten nog steeds onze argumenten, maar vóór onze haakjes hebben we het type waaraan we deze lambda gaan hechten.

Om dit patroon van a te gebruiken Draad we noemen de Type.lambdaName (argumenten)om ons ‘nog een’ voorbeeld te noemen:

fun extensionString (arg: String, num: Int): String {val another: String. (Int) -> String = {this + it} return arg.another (num)}

2.3. Terugkeren van een Lambda

De laatste uitdrukking is de waarde die wordt geretourneerd nadat een lambda is uitgevoerd:

val berekenGrade = {grade: Int -> when (grade) {in 0..40 -> "Fail" in 41..70 -> "Pass" in 71..100 -> "Distinction" else -> false}}

De laatste manier is om gebruik te maken van de anonieme functiedefinitie - we moeten de argumenten en het retourneringstype expliciet definiëren en kunnen de retourinstructie op dezelfde manier gebruiken als elke methode:

val berekenGrade = fun (cijfer: Int): String {if (cijfer 100) {return "Error"} else if (cijfer <40) {return "Fail"} else if (cijfer <70) {return "Pass"} return "Onderscheid"}

3. het

Een afkorting van een lambda met één argument is het gebruik van het trefwoord ‘het'. Deze waarde vertegenwoordigt elk afzonderlijk argument dat we doorgeven aan de lambda-functie.

We zullen hetzelfde doen voor elk methode op de volgende array van Ints:

val array = arrayOf (1, 2, 3, 4, 5, 6)

We kijken eerst naar de handvorm van de lambda-functie, gevolgd door de steno-vorm van dezelfde code, waarbij ‘het’Vertegenwoordigt elk element in de volgende array.

Met de hand:

array.forEach {item -> println (item * 4)}

Steno:

array.forEach {println (it * 4)}

4. Implementeren van Lambdas

We zullen heel kort bespreken hoe je een lambda aanroept die binnen het bereik valt en hoe je een lambda als argument kunt doorgeven.

Als een lambda-object eenmaal binnen het bereik is, noem het dan als elke andere in-scope-methode, met behulp van de naam gevolgd door haakjes en eventuele argumenten:

fun invokeLambda (lambda: (Double) -> Boolean): Boolean {return lambda (4.329)}

Als we een lambda als argument moeten doorgeven aan een methode van hogere orde, hebben we vijf opties.

4.1. Lambda-objectvariabele

Met behulp van een bestaand lambda-object zoals gedeclareerd in sectie 2, geven we het object door aan de methode zoals we dat zouden doen met elk ander argument:

@Test plezier whenPassingALambdaObject_thenCallTriggerLambda () {val lambda = {arg: Double -> arg == 4.329} val resultaat = invokeLambda (lambda) assertTrue (resultaat)}

4.2. Lambda Letterlijk

In plaats van de lambda aan een variabele toe te wijzen, kunnen we de letterlijke rechtstreeks doorgeven aan de methodeaanroep:

Test plezier whenPassingALambdaLiteral_thenCallTriggerLambda () {val resultaat = invokeLambda ({true}) assertTrue (resultaat)}

4.3. Lambda Letterlijk buiten de haakjes

Een ander patroon voor lambda-letterlijke waarden, aangemoedigd door JetBrains, is om de lambda door te geven als het laatste argument aan een methode en de lambda buiten de methodeaanroep te plaatsen:

@Test plezier whenPassingALambdaLiteralOutsideBrackets_thenCallTriggerLambda () {val resultaat = invokeLambda {arg -> arg.isNaN ()} assertFalse (resultaat)}

4.4. Methodeverwijzingen

Ten slotte hebben we de mogelijkheid om methodeverwijzingen te gebruiken. Dit zijn verwijzingen naar bestaande methoden.

In ons onderstaande voorbeeld nemen we Dubbel :: isFinite. Die functie krijgt dan dezelfde structuur als een lambda, maar het is van het type K Functie 1 aangezien het één argument heeft, neemt het een Dubbele en retourneert een Boolean:

@Test plezier whenPassingAFunctionReference_thenCallTriggerLambda () {val reference = Double :: isFinite val resultaat = invokeLambda (referentie) assertTrue (resultaat)}

5. Kotlin Lambda op Java

Kotlin gebruikt gegenereerde functie-interfaces om samen te werken met Java. Ze bestaan ​​hier in de Kotlin-broncode.

We hebben een limiet op het aantal argumenten dat kan worden doorgegeven met deze gegenereerde klassen. De huidige limiet is 22; vertegenwoordigd door de interface Functie 22.

De structuur van een Functie interface's generiek is dat het nummer en het aantal argumenten voor de lambda vertegenwoordigt, dan is dat aantal klassen het argument Typen in volgorde.

Het laatste generieke argument is het retourtype:

import kotlin.jvm.functions. * public interface Function1: Functie {public operator fun invoke (p1: P1): R}

Als er geen retourtype is gedefinieerd binnen de Kotlin-code, retourneert de lambda een Kotlin Eenheid. De Java-code moet de klasse importeren uit het kotlin pakket en retourneren met nul.

Hieronder ziet u een voorbeeld van het aanroepen van een Kotlin Lambda vanuit een project dat deels Kotlin en deels Java is:

import kotlin.Unit; import kotlin.jvm.functions.Function1; ... nieuwe Function1 () {@Override openbare eenheid aanroepen (klant c) {AnalyticsManager.trackFacebookLogin (c.getCreated ()); null teruggeven; }} 

Als we Java8 gebruiken, gebruiken we een Java-lambda in plaats van een Functie anonieme klas:

@Test ongeldig gegevenJava8_whenUsingLambda_thenReturnLambdaResult () {assertTrue (LambdaKt.takeLambda (c -> c> = 0)); }

6. Anonieme innerlijke klassen

Kotlin heeft twee interessante manieren om met anonieme innerlijke klassen te werken.

6.1. Object expressie

Bij het aanroepen van een Kotlin Inner Anonymous Class of een Java Anonymous Class die uit meerdere methoden bestaat, moeten we een Object Expression implementeren.

Om dit te demonstreren, nemen we een eenvoudige interface en een klasse die een implementatie van die interface nodig heeft en de methoden aanroept die afhankelijk zijn van een Boolean argument:

class Processor {interface ActionCallback {fun success (): String fun failure (): String} fun performEvent (decision: Boolean, callback: ActionCallback): String {return if (decision) {callback.success ()} else {callback.failure ()}}}

Om nu een anonieme innerlijke klasse te bieden, moeten we de "object" -syntaxis gebruiken:

@Test plezier gegevenMultipleMethods_whenCallingAnonymousFunction_thenTriggerSuccess () {val result = Processor (). PerformEvent (true, object: Processor.ActionCallback {override fun success () = "Succes" override fun failure () = "Failure"}) assertEquals ("Succes", resultaat) }

6.2. Lambda-expressie

Aan de andere kant kunnen we ook de mogelijkheid hebben om in plaats daarvan een lambda te gebruiken. Het gebruik van lambda's in plaats van een anonieme innerlijke klasse heeft bepaalde voorwaarden:

  1. De klasse is een implementatie van een Java-interface (geen Kotlin-interface)
  2. de interface moet max

Als aan beide voorwaarden is voldaan, kunnen we in plaats daarvan een lambda-uitdrukking gebruiken.

De lambda zelf zal evenveel argumenten aannemen als de enkele methode van de interface.

Een bekend voorbeeld is het gebruik van een lambda in plaats van een standaard Java Klant:

val list = ArrayList (2) list.stream () .forEach ({i -> println (i)})

7. Conclusie

Hoewel ze syntactisch vergelijkbaar zijn, zijn Kotlin- en Java-lambda's totaal verschillende functies. Als het zich richt op Java 6, moet Kotlin zijn lambda's omzetten in een structuur die kan worden gebruikt binnen JVM 1.6.

Desondanks zijn de best practices van Java 8 lambda's nog steeds van toepassing.

Meer over best practices voor lambda hier.

Codefragmenten zijn, zoals altijd, te vinden op GitHub.