Lambda-Ausdrücke in Kotlin

1. Übersicht

In diesem Artikel werden wir Lambdas in der Kotlin-Sprache untersuchen. Denken Sie daran, dass Lambdas nicht nur in Kotlin vorkommen und es viele Jahre in vielen anderen Sprachen gibt.

Lambdas-Ausdrücke sind im Wesentlichen anonyme Funktionen, die wir als Werte behandeln können. Wir können sie beispielsweise als Argumente an Methoden übergeben, sie zurückgeben oder andere Aktionen ausführen, die wir mit einem normalen Objekt ausführen können .

2. Definieren eines Lambda

Wie wir sehen werden, sind Kotlin Lambdas Java Lambdas sehr ähnlich. Weitere Informationen zur Arbeit mit Java Lambdas und einige bewährte Methoden finden Sie hier.

Um ein Lambda zu definieren, müssen wir uns an die Syntax halten:

val lambdaName : Type = { argumentList -> codeBody }

Der einzige Teil eines Lambda, der nicht optional ist, ist der CodeBody.

Die Argumentliste kann übersprungen werden, wenn höchstens ein Argument definiert wird, und der Typ kann häufig vom Kotlin-Compiler abgeleitet werden. Wir brauchen nicht immer auch eine Variable, das Lambda kann direkt als Methodenargument übergeben werden.

Der Typ des letzten Befehls innerhalb eines Lambda-Blocks ist der zurückgegebene Typ.

2.1. Typ Inferenz

Kotlins Typinferenz ermöglicht die Auswertung des Lambda-Typs durch den Compiler.

Das Schreiben eines Lambda, das das Quadrat einer Zahl erzeugt, lautet wie folgt:

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

Kotlin bewertet das obige Beispiel als eine Funktion, die ein Int nimmt und ein Int zurückgibt : (Int) -> Int

Wenn wir ein Lambda erstellen wollten, das seine einzelnen Argumentnummern mit 100 multipliziert, wird dieser Wert als String zurückgegeben:

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

Kotlin wird verstehen, dass dieses Lambda vom Typ (Int) -> String ist .

2.2. Typdeklaration

Gelegentlich kann Kotlin unsere Typen nicht ableiten und wir müssen den Typ für unser Lambda explizit deklarieren. so wie wir es mit jedem anderen Typ können.

Das Muster ist Eingabe -> Ausgabe . Wenn der Code jedoch keinen Wert zurückgibt, verwenden wir den Typ Einheit :

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

Wir können Lambdas als Klassenerweiterungen verwenden:

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

Das hier verwendete Muster unterscheidet sich geringfügig von den anderen von uns definierten Lambdas. Unsere Klammern enthalten immer noch unsere Argumente, aber vor unseren Klammern haben wir den Typ, an den wir dieses Lambda anhängen werden.

Um dieses Muster aus einem String zu verwenden , rufen wir Type.lambdaName (Argumente) auf, um unser 'anderes' Beispiel aufzurufen:

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

2.3. Rückkehr von einem Lambda

Der letzte Ausdruck ist der Wert, der zurückgegeben wird, nachdem ein Lambda ausgeführt wurde:

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

Der letzte Weg besteht darin, die anonyme Funktionsdefinition zu nutzen. Wir müssen die Argumente und den Rückgabetyp explizit definieren und können die return-Anweisung wie jede andere Methode verwenden:

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. es

Eine Abkürzung eines einzelnen Arguments Lambda ist die Verwendung des Schlüsselworts ' it' . Dieser Wert repräsentiert jedes einzelne Argument, das wir an die Lambda-Funktion übergeben.

Wir werden dieselbe forEach- Methode für das folgende Array von Ints ausführen :

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

Wir werden uns zuerst die Langschriftform der Lambda-Funktion ansehen, gefolgt von der Kurzform desselben Codes, wobei ' es ' jedes Element im folgenden Array darstellt.

Langschrift:

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

Kurzschrift:

array.forEach { println(it * 4) }

4. Implementierung von Lambdas

Wir werden sehr kurz darauf eingehen, wie man ein Lambda nennt, das im Geltungsbereich liegt, und wie man ein Lambda als Argument übergibt.

Wenn sich ein Lambda-Objekt im Gültigkeitsbereich befindet, rufen Sie es wie jede andere Methode im Gültigkeitsbereich auf. Verwenden Sie dazu seinen Namen, gefolgt von Klammern und Argumenten:

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

Wenn wir ein Lambda als Argument an eine Methode höherer Ordnung übergeben müssen, haben wir fünf Möglichkeiten.

4.1. Lambda-Objektvariable

Unter Verwendung eines vorhandenen Lambda-Objekts, wie in Abschnitt 2 deklariert, übergeben wir das Objekt wie bei jedem anderen Argument an die Methode:

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

4.2. Lambda Literal

Anstatt das Lambda einer Variablen zuzuweisen, können wir das Literal direkt an den Methodenaufruf übergeben:

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

4.3. Lambda-Literal außerhalb der Klammern

Ein weiteres von JetBrains empfohlenes Muster für Lambda-Literale besteht darin, das Lambda als letztes Argument an eine Methode zu übergeben und das Lambda außerhalb des Methodenaufrufs zu platzieren:

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

4.4. Methodenreferenzen

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.

Das Lambda selbst akzeptiert so viele Argumente wie die einzelne Methode der Schnittstelle.

Ein häufiges Beispiel wäre die Verwendung eines Lambda anstelle eines Standard-Java- Consumer:

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

7. Fazit

Kotlin- und Java-Lambdas sind zwar syntaktisch ähnlich, weisen jedoch völlig unterschiedliche Merkmale auf. Beim Targeting von Java 6 muss Kotlin seine Lambdas in eine Struktur umwandeln, die in JVM 1.6 verwendet werden kann.

Trotzdem gelten weiterhin die Best Practices von Java 8 Lambdas.

Weitere Informationen zu bewährten Methoden für Lambda finden Sie hier.

Code-Schnipsel finden Sie wie immer auf GitHub.