Espressioni lambda in Kotlin

1. Panoramica

In questo articolo, esploreremo Lambdas nella lingua Kotlin. Tieni presente che i lambda non sono esclusivi di Kotlin e sono in circolazione da molti anni in molte altre lingue.

Le espressioni Lambdas sono essenzialmente funzioni anonime che possiamo trattare come valori: possiamo, ad esempio, passarle come argomenti ai metodi, restituirle o fare qualsiasi altra cosa che potremmo fare con un oggetto normale.

2. Definizione di un Lambda

Come vedremo, Kotlin Lambdas sono molto simili a Java Lambdas. Puoi trovare ulteriori informazioni su come lavorare con Java Lambdas e alcune best practice qui.

Per definire un lambda, dobbiamo attenerci alla sintassi:

val lambdaName : Type = { argumentList -> codeBody }

L'unica parte di un lambda che non è opzionale è codeBody.

L'elenco degli argomenti può essere ignorato quando si definisce al massimo un argomento e il tipo può spesso essere dedotto dal compilatore Kotlin. Non sempre abbiamo bisogno anche di una variabile, lambda può essere passato direttamente come argomento del metodo.

Il tipo dell'ultimo comando all'interno di un blocco lambda è il tipo restituito.

2.1. Digitare inferenza

L'inferenza del tipo di Kotlin consente al compilatore di valutare il tipo di lambda.

Scrivere un lambda che produce il quadrato di un numero sarebbe come scritto come:

val square = { number: Int -> number * number } val nine = square(3)

Kotlin valuterà l'esempio precedente come una funzione che accetta un Int e restituisce un Int: (Int) -> Int

Se volessimo creare un lambda che moltiplica i suoi numeri di argomento singolo per 100, restituisce quel valore come stringa:

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

Kotlin capirà che questo lambda è di tipo (Int) -> String .

2.2. Dichiarazione di tipo

Occasionalmente Kotlin non può dedurre i nostri tipi e dobbiamo dichiarare esplicitamente il tipo per il nostro lambda; proprio come possiamo con qualsiasi altro tipo.

Il pattern è input -> output , tuttavia, se il codice non restituisce alcun valore utilizziamo il tipo Unit :

val that : Int -> Int = { three -> three }
val more : (String, Int) -> String = { str, int -> str + int }
val noReturn : Int -> Unit = { num -> println(num) }

Possiamo usare lambda come estensioni di classe:

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

Il modello che usiamo qui è leggermente diverso dagli altri lambda che abbiamo definito. Le nostre parentesi contengono ancora i nostri argomenti, ma prima delle nostre parentesi, abbiamo il tipo a cui collegheremo questo lambda.

Per utilizzare questo modello da una stringa chiamiamo Type.lambdaName (argomenti) in modo da chiamare il nostro esempio "un altro":

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

2.3. Di ritorno da una Lambda

L'espressione finale è il valore che verrà restituito dopo l'esecuzione di un lambda:

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

Il modo finale è sfruttare la definizione della funzione anonima: dobbiamo definire gli argomenti e il tipo di ritorno in modo esplicito e possiamo usare l'istruzione return come qualsiasi altro metodo:

val calculateGrade = fun(grade: Int): String { if (grade  100) { return "Error" } else if (grade < 40) { return "Fail" } else if (grade < 70) { return "Pass" } return "Distinction" }

3. it

Una scorciatoia di un singolo argomento lambda consiste nell'usare la parola chiave " it" . Questo valore rappresenta qualsiasi argomento che passiamo alla funzione lambda.

Eseguiremo lo stesso metodo forEach sul seguente array di Ints :

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

Per prima cosa esamineremo la forma a mano lunga della funzione lambda, seguita dalla forma abbreviata dello stesso codice, dove " it " rappresenterà ogni elemento nel seguente array.

Longhand:

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

Abbreviazione:

array.forEach { println(it * 4) }

4. Implementazione di Lambda

Tratteremo molto brevemente come chiamare un lambda che è nell'ambito e come passare un lambda come argomento.

Una volta che un oggetto lambda è nell'ambito, chiamalo come qualsiasi altro metodo in ambito, utilizzando il suo nome seguito da parentesi e qualsiasi argomento:

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

Se abbiamo bisogno di passare un lambda come argomento in un metodo di ordine superiore, abbiamo cinque opzioni.

4.1. Variabile oggetto Lambda

Usando un oggetto lambda esistente come dichiarato nella sezione 2, passiamo l'oggetto nel metodo come faremmo con qualsiasi altro argomento:

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

4.2. Lambda Literal

Invece di assegnare lambda a una variabile, possiamo passare il letterale direttamente nella chiamata al metodo:

Test fun whenPassingALambdaLiteral_thenCallTriggerLambda() { val result = invokeLambda({ true }) assertTrue(result) }

4.3. Valore letterale lambda fuori dalle parentesi

Un altro modello per i valori letterali lambda incoraggiati da JetBrains: passare lambda come ultimo argomento di un metodo e posizionare il lambda fuori dalla chiamata del metodo:

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

4.4. Riferimenti al metodo

Finally, we have the option of using method references. These are references to existing methods.

In our example below, we take Double::isFinite. That function then takes on the same structure as a lambda, however, it's of type KFunction1 as it has one argument, takes in a Double and returns a Boolean:

@Test fun whenPassingAFunctionReference_thenCallTriggerLambda() { val reference = Double::isFinite val result = invokeLambda(reference) assertTrue(result) }

5. Kotlin Lambda in Java

Kotlin uses generated function interfaces to interop with Java. They exist in the Kotlin source code here.

We have a limit on the number of arguments that can be passed in with these generated classes. The current limit is 22; represented by the interface Function22.

The structure of a Function interface's generics is that the number and represents the number of arguments to the lambda, then that number of classes will be the argument Types in order.

The final generic argument is the return type:

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

When there is no return type defined within the Kotlin code, then the lambda returns a Kotlin Unit. The Java code must import the class from the kotlin package and return with null.

Below is an example of calling a Kotlin Lambda from a project that is part Kotlin and part Java:

import kotlin.Unit; import kotlin.jvm.functions.Function1; ... new Function1() { @Override public Unit invoke(Customer c) { AnalyticsManager.trackFacebookLogin(c.getCreated()); return null; } } 

When using Java8, we use a Java lambda instead of a Function anonymous class:

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

6. Anonymous Inner Classes

Kotlin has two interesting ways of working with Anonymous Inner Classes.

6.1. Object Expression

When calling a Kotlin Inner Anonymous Class or a Java Anonymous Class comprised of multiple methods we must implement an Object Expression.

To demonstrate this, we'll take a simple interface and a class that takes an implementation of that interface and calls the methods dependent on a 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() } } }

Now to provide an anonymous inner class, we need to use the “object” syntax:

@Test fun givenMultipleMethods_whenCallingAnonymousFunction_thenTriggerSuccess() { val result = Processor().performEvent(true, object : Processor.ActionCallback { override fun success() = "Success" override fun failure() = "Failure" }) assertEquals("Success", result) }

6.2. Lambda Expression

On the other hand, we may also have the option of using a lambda instead. Using lambdas in lieu of an Anonymous Inner Class has certain conditions:

  1. The class is an implementation of a Java interface (not a Kotlin one)
  2. the interface must have max

If both of these conditions are met, we may use a lambda expression instead.

Lo stesso lambda prenderà tanti argomenti quanti ne fa il singolo metodo dell'interfaccia.

Un esempio comune potrebbe essere l'utilizzo di un lambda invece di un consumatore Java standard :

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

7. Conclusione

Sebbene sintatticamente simili, Kotlin e Java lambda sono funzionalità completamente diverse. Quando si prende di mira Java 6, Kotlin deve trasformare i suoi lambda in una struttura che può essere utilizzata all'interno di JVM 1.6.

Nonostante ciò, si applicano ancora le migliori pratiche di Java 8 lambda.

Maggiori informazioni sulle migliori pratiche lambda qui.

Gli snippet di codice, come sempre, possono essere trovati su GitHub.