Lambda-Ausdrücke in Kotlin

Lambda-Ausdrücke in Kotlin

1. Überblick

In diesem Artikel werden wir Lambdas in der Kotlin-Sprache untersuchen. Denken Sie daran, dass Lambdas nicht nur bei Kotlin erhältlich sind und bereits seit vielen Jahren in vielen anderen Sprachen erhältlich sind.

Lambdas Expressions are essentially anonymous functions that we can treat as values – können wir sie beispielsweise als Argumente an Methoden übergeben, zurückgeben oder alles andere tun, was wir mit einem normalen Objekt tun könnten.

2. Lambda definieren

Wie wir sehen werden, sind Kotlin Lambdas Java Lambdas sehr ähnlich. Sie erfahren mehr über die Arbeit mit Java Lambdas und einige Best Practiceshere.

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 derType -Scan wird häufig vom Kotlin-Compiler abgeleitet. We don’t always need a variable as well, the lambda can be passed directly as a method argument.

Der Typ des letzten Befehls in einem Lambda-Block ist der zurückgegebene Typ.

2.1. Typ Inferenz

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

Ein Lambda zu schreiben, das das Quadrat einer Zahl ergibt, wäre so geschrieben wie:

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

Kotlin bewertet das obige Beispiel als eine Funktion, bei der einInt and einInt:(Int) → Int zurückgibt

Wenn wir ein Lambda erstellen wollten, das seine einzelnen Argumentnummern mit 100 multipliziert, wird dieser Wert alsString: 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 istinput → output. Wenn der Code jedoch keinen Wert zurückgibt, verwenden wir den TypUnit:

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 einemString we zu verwenden, rufen SieType.lambdaName(arguments)so 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
    }
}

Die letzte Möglichkeit 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 < 0 || grade > 100) {
        return "Error"
    } else if (grade < 40) {
        return "Fail"
    } else if (grade < 70) {
        return "Pass"
    }

    return "Distinction"
}

3. it

Eine Abkürzung eines einzelnen Arguments Lambda ist die Verwendung des Schlüsselworts 'it'. This value represents any lone that argument we pass to the lambda function.

Wir führen dieselbeforEach -Smethod für das folgende Array vonInts durch:

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 'it' jedes Element im folgenden Array darstellt.

Langschrift:

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

Stenografie:

array.forEach { println(it * 4) }

4. Implementierung von Lambdas

Wir werden sehr kurz darauf eingehen, wie man ein Lambda nennt, das in den Geltungsbereich fällt, und wie man ein Lambda als Argument übergibt.

Sobald sich ein Lambda-Objekt im Gültigkeitsbereich befindet, rufen Sie es wie jede andere in-scope-Methode auf. Verwenden Sie dazu den Namen, gefolgt von eckigen 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 Optionen.

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 unterstütztes 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

Schließlich haben wir die Möglichkeit, Methodenreferenzen zu verwenden. Dies sind Verweise auf vorhandene Methoden.

In unserem Beispiel unten nehmen wirDouble::isFinite. Diese Funktion hat dann die gleiche Struktur wie ein Lambda. Sie hat jedoch den TypKFunction1<Double, Boolean> , da sie ein Argument hat undDouble and aufnimmt undBoolean zurückgibt:

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

    assertTrue(result)
}

5. Kotlin Lambda auf Java

Kotlin verwendet generierte Funktionsschnittstellen, um mit Java zu interagieren. Sie existieren im Kotlin-Quellcodehere.

Die Anzahl der Argumente, die mit diesen generierten Klassen übergeben werden können, ist begrenzt. Die aktuelle Grenze ist 22; dargestellt durch die SchnittstelleFunction22.

Die Struktur der Generika einerFunction -Sinterschnittstelle besteht darin, dass die Anzahl und die Anzahl der Argumente für das Lambda die Anzahl der Klassen in der Reihenfolge der Argumenttypen sind.

Das letzte generische Argument ist der Rückgabetyp:

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 UnitDer Java-Code muss die Klasse aus demkotlin -Spackage importieren und mitnull zurückgeben.

Unten sehen Sie ein Beispiel für den Aufruf einer Kotlin-Lambda aus einem Projekt, das aus Kotlin und Java besteht:

import kotlin.Unit;
import kotlin.jvm.functions.Function1;
...
new Function1() {
    @Override
    public Unit invoke(Customer c) {

        AnalyticsManager.trackFacebookLogin(c.getCreated());

        return null;
    }
}

Bei Verwendung von Java8 verwenden wir ein Java-Lambda anstelle einer sanonymen KlasseFunction :

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

6. Anonyme innere Klassen

Kotlin hat zwei interessante Möglichkeiten, mit anonymen inneren Klassen zu arbeiten.

6.1. Objektausdruck

Beim Aufrufen einer inneren anonymen Kotlin-Klasse oder einer anonymen Java-Klasse, die aus mehreren Methoden besteht, müssen wir einen Objektausdruck implementieren.

Um dies zu demonstrieren, verwenden wir eine einfache Schnittstelle und eine Klasse, die eine Implementierung dieser Schnittstelle übernimmt und die Methoden aufruft, die von einemBoolean -Sargument abhängig sind:

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()
        }
    }
}

Um nun eine anonyme innere Klasse bereitzustellen, müssen wir die "Objekt" -Syntax verwenden:

@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-Ausdruck

Andererseits haben wir möglicherweise auch die Möglichkeit, stattdessen ein Lambda zu verwenden. Using lambdas in lieu of an Anonymous Inner Class has certain conditions:

  1. Die Klasse ist eine Implementierung einer Java-Schnittstelle (keine Kotlin-Schnittstelle).

  2. Die Schnittstelle darf max

Wenn beide Bedingungen erfüllt sind, können wir stattdessen einen Lambda-Ausdruck verwenden.

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-JavaConsumer:

val list = ArrayList(2)

list.stream()
  .forEach({ i -> println(i) })

7. Conclusion

Kotlin- und Java-Lambdas sind syntaktisch zwar ähnlich, weisen jedoch völlig unterschiedliche Merkmale auf. Bei der Ausrichtung auf 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 Best Practices für Lambdahere.

Code-Schnipsel finden Sie wie immer inover on GitHub.