Expressions lambda dans Kotlin

Expressions lambda à Kotlin

1. Vue d'ensemble

Dans cet article, nous allons explorer Lambdas en langue Kotlin. N'oubliez pas que les lambdas ne sont pas uniques à Kotlin et existent depuis de nombreuses années dans de nombreuses autres langues.

Lambdas Expressions are essentially anonymous functions that we can treat as values –, nous pouvons, par exemple, les passer comme arguments aux méthodes, les renvoyer ou faire toute autre chose que nous pourrions faire avec un objet normal.

2. Définition d'un Lambda

Comme nous le verrons, les Kotlin Lambdas sont très similaires aux Java Lambdas. Vous pouvez en savoir plus sur la façon de travailler avec Java Lambdas et quelques bonnes pratiqueshere.

Pour définir un lambda, nous devons nous en tenir à la syntaxe:

val lambdaName : Type = { argumentList -> codeBody }

La seule partie d'un lambda qui n'est pas facultative est le codeBody.

La liste d'arguments peut être ignorée lors de la définition d'au plus un argument et l'analyseType est souvent déduite par le compilateur Kotlin. We don’t always need a variable as well, the lambda can be passed directly as a method argument.

Le type de la dernière commande dans un bloc lambda est le type renvoyé.

2.1. Inférence de type

L'inférence de type de Kotlin permet au type d'un lambda d'être évalué par le compilateur.

L'écriture d'un lambda qui produit le carré d'un nombre serait la suivante:

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

Kotlin évaluera l'exemple ci-dessus comme étant une fonction qui prend unInt able renvoie unInt:(Int) → Int

Si nous voulions créer un lambda qui multiplie ses numéros d'argument unique par 100, retourne cette valeur sous forme deString:

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

Kotlin comprendra que ce lambda est de type(Int) → String.

2.2. Déclaration de type

Parfois, Kotlin ne peut pas déduire nos types et nous devons explicitement déclarer le type pour notre lambda; comme nous pouvons avec n'importe quel autre type.

Le modèle estinput → output, cependant, si le code ne renvoie aucune valeur, nous utilisons le typeUnit:

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

Nous pouvons utiliser des lambdas comme extensions de classe:

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

Le modèle que nous utilisons ici est légèrement différent des autres lambdas que nous avons définis. Nos crochets contiennent toujours nos arguments, mais avant nos crochets, nous avons le type auquel nous allons attacher ce lambda.

Pour utiliser ce modèle à partir d'unString we, appelez leType.lambdaName(arguments)so pour appeler notre "autre" exemple:

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

    return arg.another(num)
}

2.3. Revenant d'un lambda

L'expression finale est la valeur qui sera retournée après l'exécution d'un lambda:

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

La dernière méthode consiste à tirer parti de la définition de la fonction anonyme - nous devons définir explicitement les arguments et le type de retour et utiliser l'instruction return de la même manière que n'importe quelle méthode:

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

Un raccourci pour un seul argument lambda consiste à utiliser le mot-clé ‘it'. This value represents any lone that argument we pass to the lambda function.

Nous allons exécuter la même méthodeforEach ur le tableau suivant deInts:

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

Nous allons d'abord examiner la forme longue de la fonction lambda, suivie de la forme abrégée du même code, où «it» représentera chaque élément du tableau suivant.

Écriture normale:

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

Sténographie:

array.forEach { println(it * 4) }

4. Implémentation de Lambdas

Nous allons expliquer très brièvement comment appeler un lambda qui est dans la portée ainsi que comment passer un lambda en tant qu'argument.

Une fois qu'un objet lambda est dans la portée, appelez-le comme n'importe quelle autre méthode dans la portée, en utilisant son nom suivi de crochets et de tous les arguments:

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

Si nous devons passer un lambda comme argument dans une méthode d'ordre supérieur, nous avons cinq options.

4.1. Variable d'objet Lambda

En utilisant un objet lambda existant comme déclaré dans la section 2, nous passons l'objet dans la méthode comme nous le ferions avec n'importe quel autre argument:

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

    val result = invokeLambda(lambda)

    assertTrue(result)
}

4.2. Lambda Literal

Au lieu d’assigner le lambda à une variable, nous pouvons passer le littéral directement à l’appel de la méthode:

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

    assertTrue(result)
}

4.3. Lambda Literal En dehors des crochets

Un autre modèle encouragé par JetBrains pour les littéraux lambda consiste à passer le lambda comme dernier argument d’une méthode et à placer le lambda en dehors de l’appel de la méthode:

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

    assertFalse(result)
}

4.4. Références de méthode

Enfin, nous avons la possibilité d’utiliser des références de méthodes. Ce sont des références à des méthodes existantes.

Dans notre exemple ci-dessous, nous prenonsDouble::isFinite. Cette fonction prend alors la même structure qu’un lambda, cependant, elle est de typeKFunction1<Double, Boolean>  car elle a un argument, prend unDouble and renvoie unBoolean:

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

    assertTrue(result)
}

5. Kotlin Lambda en Java

Kotlin utilise des interfaces de fonctions générées pour interagir avec Java. Ils existent dans le code source de Kotlinhere.

Nous avons une limite sur le nombre d'arguments pouvant être passés avec ces classes générées. La limite actuelle est de 22; représenté par l'interfaceFunction22.

La structure des génériques d’une interfaceFunction est que le nombre et représente le nombre d’arguments du lambda, alors ce nombre de classes sera l’argument Types dans l’ordre.

L'argument générique final est le type de retour:

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 UnitLe code Java doit importer la classe à partir du spackagekotlin et retourner avecnull.

Vous trouverez ci-dessous un exemple d’appel d’un Kotlin Lambda à partir d’un projet appartenant à la fois à Kotlin et à Java:

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

        AnalyticsManager.trackFacebookLogin(c.getCreated());

        return null;
    }
}

Lors de l'utilisation de Java8, nous utilisons un lambda Java au lieu d'une classe sanonymousFunction :

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

6. Classes intérieures anonymes

Kotlin a deux façons intéressantes de travailler avec les classes internes anonymes.

6.1. Expression d'objet

Lorsque vous appelez une classe anonyme anonyme Kotlin ou une classe anonyme Java composée de plusieurs méthodes, vous devez implémenter une expression d'objet.

Pour illustrer cela, nous allons prendre une interface simple et une classe qui prend une implémentation de cette interface et appelle les méthodes dépendant d'un sargumentBoolean :

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

Maintenant, pour fournir une classe interne anonyme, nous devons utiliser la syntaxe «object»:

@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. Expression Lambda

D'autre part, nous pouvons également avoir la possibilité d'utiliser un lambda à la place. Using lambdas in lieu of an Anonymous Inner Class has certain conditions:

  1. La classe est une implémentation d'une interface Java (pas une interface Kotlin)

  2. l'interface doit avoir max

Si ces deux conditions sont remplies, nous pouvons utiliser une expression lambda à la place.

Le lambda lui-même prendra autant d'arguments que la méthode unique de l'interface.

Un exemple courant serait d'utiliser un lambda au lieu d'un JavaConsumer: standard

val list = ArrayList(2)

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

7. Conclusion

Bien que syntaxiquement similaires, les lambda de Kotlin et de Java sont des fonctionnalités complètement différentes. Lorsqu'il cible Java 6, Kotlin doit transformer ses lambdas en une structure utilisable dans JVM 1.6.

Malgré cela, les meilleures pratiques de Java 8 lambdas sont toujours applicables.

En savoir plus sur les meilleures pratiques lambdahere.

Des extraits de code, comme toujours, peuvent être trouvésover on GitHub.