186 votes

Exemple de quand utiliser run, let, apply, also et with sur Kotlin

Je souhaite avoir un bon exemple pour chaque fonction exécuter, laisser, appliquer, aussi, avec

J'ai lu cet article mais il manque toujours un exemple

0 votes

Utilisez kotlinlang.org/docs/scope-functions.html#fonction-selection pour avoir un aperçu rapide du récepteur et de la valeur de retour.

227voto

Lovis Points 3935

Toutes ces fonctions sont utilisées pour changer la portée de la fonction courante / de la variable. Elles sont utilisées pour garder les choses qui vont ensemble à un seul endroit (principalement les initialisations).

Voici quelques exemples :

run - renvoie tout ce que vous voulez et re-scope la variable sur laquelle il est utilisé pour this

val password: Password = PasswordGenerator().run {
       seed = "someString"
       hash = {s -> someHash(s)}
       hashRepetitions = 1000

       generate()
   }

Le générateur de mot de passe est maintenant rescopé comme this et nous pouvons donc définir seed , hash y hashRepetitions sans utiliser de variable. generate() retournera une instance de Password .

apply est similaire, mais il retournera this :

val generator = PasswordGenerator().apply {
       seed = "someString"
       hash = {s -> someHash(s)}
       hashRepetitions = 1000
   }
val pasword = generator.generate()

C'est particulièrement utile pour remplacer le modèle Builder, et si vous souhaitez réutiliser certaines configurations.

let - principalement utilisé pour éviter les vérifications de nullité, mais peut également être utilisé en remplacement de run . La différence, c'est que this sera toujours le même qu'avant et vous accédez à la variable re-scopée en utilisant it :

val fruitBasket = ...

apple?.let {
  println("adding a ${it.color} apple!")
  fruitBasket.add(it)
}

Le code ci-dessus ajoutera la pomme au panier uniquement si elle n'est pas nulle. Notez également que it est maintenant n'est plus facultatif de sorte que vous ne rencontrerez pas d'exception NullPointerException ici (c'est-à-dire que vous n'avez pas besoin d'utiliser ?. pour accéder à ses attributs)

also - utilisez-le quand vous voulez utiliser apply mais ne veulent pas faire de l'ombre this

class FruitBasket {
    private var weight = 0

    fun addFrom(appleTree: AppleTree) {
        val apple = appleTree.pick().also { apple ->
            this.weight += apple.weight
            add(apple)
        }
        ...
    }
    ...
    fun add(fruit: Fruit) = ...
}

Utilisation de apply ici serait l'ombre this de sorte que this.weight ferait référence à la pomme, et no au panier de fruits.


Note : J'ai pris sans vergogne les exemples de mon blog

20 votes

Pour tous ceux qui, comme moi, ont été surpris par le premier code, la dernière ligne d'une lambda est considérée comme une valeur de retour en Kotlin.

104voto

haxpor Points 185

Il y a quelques autres articles comme aquí y aquí qui valent le coup d'œil.

Je pense qu'il s'agit de savoir quand on a besoin d'un texte plus court et plus concis en quelques lignes, et d'éviter les branchements ou la vérification d'instructions conditionnelles (comme if not null, then do this).

J'adore ce tableau simple, je l'ai donc mis en lien ici. Vous pouvez le voir à partir de este comme écrit par Sebastiano Gottardo.

enter image description here

Veuillez également consulter le graphique qui accompagne mon explication ci-dessous.

Concept

Je pense qu'il s'agit d'un jeu de rôle à l'intérieur de votre bloc de code lorsque vous appelez ces fonctions + si vous voulez revenir (pour enchaîner les fonctions d'appel, ou mettre à la variable de résultat, etc).

Voici ce que je pense.

Exemple de concept

Voyons des exemples pour chacun d'entre eux ici

1.) myComputer.apply { } signifie que vous voulez agir en tant qu'acteur principal (vous voulez penser que vous êtes l'ordinateur), et que vous voulez vous récupérer (ordinateur) afin de pouvoir faire

var crashedComputer = myComputer.apply { 
    // you're the computer, you yourself install the apps
    // note: installFancyApps is one of methods of computer
    installFancyApps() 
}.crash()

Ouaip, il suffit d'installer soi-même les applications, de se planter, et de s'enregistrer comme référence pour permettre aux autres de voir et de faire quelque chose avec.

2.) myComputer.also {} signifie que vous êtes complètement sûr que vous no son ordinateur, vous êtes un outsider qui veut en faire quelque chose, et qui veut aussi que l'ordinateur soit un résultat retourné.

var crashedComputer = myComputer.also { 
    // now your grandpa does something with it
    myGrandpa.installVirusOn(it) 
}.crash()

3.) with(myComputer) { } signifie que vous êtes l'acteur principal (ordinateur), et que vous Ne le fais pas. vous voulez retrouver le résultat.

with(myComputer) {
    // you're the computer, you yourself install the apps
    installFancyApps()
}

4.) myComputer.run { } signifie que vous êtes l'acteur principal (ordinateur), et que vous Ne le fais pas. vous voulez retrouver le résultat.

myComputer.run {
    // you're the computer, you yourself install the apps
    installFancyApps()
}

mais c'est différent de with { } dans un sens très subtil que vous pouvez appeler en chaîne run { } comme les suivantes

myComputer.run {
    installFancyApps()
}.run {
    // computer object isn't passed through here. So you cannot call installFancyApps() here again.
    println("woop!")
}

Ceci est dû à run {} est une fonction d'extension, mais with { } ne l'est pas. Donc vous appelez run { } y this à l'intérieur du bloc de code sera répercutée sur le type d'objet de l'appelant. Vous pouvez voir este pour une excellente explication de la différence entre run {} y with {} .

5.) myComputer.let { } signifie que vous êtes une personne extérieure qui regarde l'ordinateur, et qui veut faire quelque chose à ce sujet sans se soucier de l'instance de l'ordinateur qui vous sera retournée.

myComputer.let {
    myGrandpa.installVirusOn(it)
}

La façon de voir les choses

J'ai tendance à regarder also y let comme quelque chose d'externe, d'extérieur. Chaque fois que vous dites ces deux mots, c'est comme si vous essayiez d'agir sur quelque chose. let installer un virus sur cet ordinateur, et also Crash it. Donc, cela résout la question de savoir si vous êtes un acteur ou non.

Pour la partie résultat, c'est clairement là. also exprime que c'est aussi une autre chose, donc vous conservez toujours la disponibilité de l'objet lui-même. Il le renvoie donc comme résultat.

Tout le reste s'associe à this . En outre, run/with n'est clairement pas intéressé par le retour de l'objet-soi. Maintenant vous pouvez les différencier tous.

Je pense que parfois, lorsque nous nous éloignons des exemples 100% programmés/logiques, nous sommes mieux placés pour conceptualiser les choses. Mais ça dépend :)

3 votes

Le diagramme dit tout ; le meilleur jusqu'à présent.

40voto

oiyio Points 1001

Il existe 6 fonctions de cadrage différentes :

  1. T.run
  2. T.let
  3. T.appliquer
  4. T.aussi
  5. con
  6. exécuter

J'ai préparé une note visuelle comme celle ci-dessous pour montrer les différences :

data class Citizen(var name: String, var age: Int, var residence: String)

enter image description here

La décision dépend de vos besoins. Les cas d'utilisation des différentes fonctions se chevauchent, de sorte que vous pouvez choisir les fonctions en fonction des conventions spécifiques utilisées dans votre projet ou votre équipe.

Bien que les fonctions scope soient un moyen de rendre le code plus concis, évitez d'en abuser : cela peut diminuer la lisibilité de votre code et entraîner des erreurs. Évitez d'imbriquer les fonctions scope et faites attention lorsque vous les enchaînez : il est facile de se tromper sur l'objet contextuel actuel et la valeur de this ou it.

Voici un autre diagramme permettant de décider lequel utiliser à partir de https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84 enter image description here

Certaines conventions sont les suivantes :

Utilisez également pour des actions supplémentaires qui ne modifient pas l'objet, comme la journalisation ou l'impression d'informations de débogage.

val numbers = mutableListOf("one", "two", "three")
 numbers
 .also { println("The list elements before adding new one: $it") }
 .add("four")

Le cas commun de appliquer est la configuration de l'objet.

val adam = Person("Adam").apply {
age = 32
city = "London"        
}
println(adam)

Si vous avez besoin d'ombres, utilisez exécuter

fun test() {
    var mood = "I am sad"

    run {
        val mood = "I am happy"
        println(mood) // I am happy
    }
    println(mood)  // I am sad
}

Si vous avez besoin de retourner l'objet récepteur lui-même, utilisez appliquer o également

10voto

Bhuvanesh BS Points 3786

laisser, également, appliquer, prendreSi, prendreSans sont des fonctions d'extension en Kotlin.

Pour comprendre ces fonctions, vous devez comprendre Fonctions d'extension y Fonctions lambda en Kotlin.

Fonction d'extension :

En utilisant la fonction d'extension, nous pouvons créer une fonction pour une classe sans hériter d'une classe.

Kotlin, à l'instar de C# et Gosu, offre la possibilité d'étendre une classe avec de nouvelles fonctionnalités sans avoir à hériter de la classe ou à utiliser ou d'utiliser un modèle de conception tel qu'un décorateur. Cela se fait par le biais de déclarations spéciales spéciales appelées extensions. Kotlin prend en charge les fonctions d'extension et les propriétés d'extension.

Donc, pour trouver si seulement les nombres dans le String vous pouvez créer une méthode comme ci-dessous sans hériter de la méthode String classe.

fun String.isNumber(): Boolean = this.matches("[0-9]+".toRegex())

vous pouvez utiliser la méthode ci-dessus fonction d'extension comme ça,

val phoneNumber = "8899665544"
println(phoneNumber.isNumber)

qui est imprimé true .

Fonctions lambda :

Les fonctions lambda sont comme les interfaces en Java. Mais en Kotlin, les fonctions lambda peuvent être passées en paramètre dans les fonctions.

Ejemplo:

fun String.isNumber(block: () -> Unit): Boolean {
    return if (this.matches("[0-9]+".toRegex())) {
        block()
        true
    } else false
}

Vous pouvez voir que le bloc est une fonction lambda et qu'il est passé en paramètre. Vous pouvez utiliser la fonction ci-dessus comme ceci,

val phoneNumber = "8899665544"
    println(phoneNumber.isNumber {
        println("Block executed")
    })

La fonction ci-dessus s'imprimera comme ceci,

Block executed
true

J'espère que vous avez maintenant une idée des fonctions d'extension et des fonctions Lambda. Nous allons maintenant passer aux fonctions d'extension une par une.

let

public inline fun <T, R> T.let(block: (T) -> R): R = block(this)

Deux types T et R utilisés dans la fonction ci-dessus.

T.let

T peut être n'importe quel objet comme la classe String. Vous pouvez donc invoquer cette fonction avec n'importe quel objet.

block: (T) -> R

Dans le paramètre de let, vous pouvez voir la fonction lambda ci-dessus. De plus, l'objet invoquant est passé comme un paramètre de la fonction. Vous pouvez donc utiliser l'objet de la classe invoquante à l'intérieur de la fonction, qui renvoie alors l'objet R (un autre objet).

Ejemplo:

val phoneNumber = "8899665544"
val numberAndCount: Pair<Int, Int> = phoneNumber.let { it.toInt() to it.count() }

Dans l'exemple ci-dessus, nous prenons Chaîne de caractères comme paramètre de sa fonction lambda et il retourne Paire en retour.

De la même manière, d'autres fonctions d'extension fonctionnent.

également

public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }

fonction d'extension also prend la classe invoquée comme paramètre de la fonction lambda et ne retourne rien.

Ejemplo:

val phoneNumber = "8899665544"
phoneNumber.also { number ->
    println(number.contains("8"))
    println(number.length)
 }

appliquer

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

Idem, mais le même objet d'invocation est passé comme la fonction, de sorte que vous pouvez utiliser les fonctions et autres propriétés sans l'appeler ou le nom du paramètre.

Ejemplo:

val phoneNumber = "8899665544"
phoneNumber.apply { 
    println(contains("8"))
    println(length)
 }

Vous pouvez voir dans l'exemple ci-dessus les fonctions de la classe String directement invoquées à l'intérieur de la fonction lambda.

takeIf

public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null

Ejemplo:

val phoneNumber = "8899665544"
val number = phoneNumber.takeIf { it.matches("[0-9]+".toRegex()) }

Dans l'exemple ci-dessus number aura une chaîne de phoneNumber seulement il correspond à la regex . Sinon, ce sera null .

takeUnless

public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null

C'est l'inverse de takeIf.

Ejemplo:

val phoneNumber = "8899665544"
val number = phoneNumber.takeUnless { it.matches("[0-9]+".toRegex()) }

number aura une chaîne de phoneNumber seulement si elle ne correspond pas à la regex . Sinon, ce sera null .

Vous pouvez voir des réponses similaires qui sont utiles ici. différence entre kotlin also, apply, let, use, takeIf et takeUnless en Kotlin

0 votes

Il y a une faute de frappe dans votre dernier exemple, vous vouliez probablement dire phoneNumber. takeUnless{} au lieu de phoneNumber. takeIf{} .

1 votes

Corrigé. Merci @Ryan Amaral

1 votes

C'est une des grandes explications, j'aurais aimé qu'elle soit encore plus courte. Mais c'est quand même très bien.

4voto

Shreck Ye Points 178

Selon mon expérience, puisque ces fonctions sont du sucre syntaxique en ligne sans différence de performance, vous devriez toujours choisir celle qui nécessite d'écrire le moins de code dans la lamda.

Pour ce faire, déterminez d'abord si vous voulez que la lambda renvoie son résultat (choisissez run / let ) ou l'objet lui-même (choisissez apply / also ) ; puis, dans la plupart des cas, lorsque la lambda est une expression unique, choisissez celles qui ont le même type de fonction de bloc que cette expression, car lorsqu'il s'agit d'une expression réceptrice, this peut être omis, lorsqu'il s'agit d'une expression de paramètre, it est plus courte que this :

val a: Type = ...

fun Type.receiverFunction(...): ReturnType { ... }
a.run/*apply*/ { receiverFunction(...) } // shorter because "this" can be omitted
a.let/*also*/ { it.receiverFunction(...) } // longer

fun parameterFunction(parameter: Type, ...): ReturnType { ... }
a.run/*apply*/ { parameterFunction(this, ...) } // longer
a.let/*also*/ { parameterFunction(it, ...) } // shorter because "it" is shorter than "this"

Toutefois, lorsque le lambda est constitué d'un mélange de ces éléments, il vous appartient alors de choisir celui qui s'intègre le mieux au contexte ou avec lequel vous vous sentez le plus à l'aise.

Utilisez également ceux qui ont une fonction de bloc de paramètres lorsque la déconstruction est nécessaire :

val pair: Pair<TypeA, TypeB> = ...

pair.run/*apply*/ {
    val (first, second) = this
    ...
} // longer
pair.let/*also*/ { (first, second) -> ... } // shorter

Voici une brève comparaison entre toutes ces fonctions, tirée du cours officiel Kotlin de JetBrains sur Coursera. Kotlin pour les développeurs Java : Difference table Simplified implementations

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X