Beispiel, wann wir run, let, apply, also und with auf Kotlin verwenden sollen

92

Ich möchte ein gutes Beispiel für jeden Funktionslauf haben, lassen, auch anwenden, mit

Ich habe diesen Artikel gelesen, aber es fehlt mir noch ein Beispiel

UmAnusorn
quelle

Antworten:

112

Alle diese Funktionen dienen zum Umschalten des Umfangs der aktuellen Funktion / der Variablen. Sie werden verwendet, um Dinge, die zusammengehören, an einem Ort zusammenzuhalten (meistens Initialisierungen).

Hier sind einige Beispiele:

run - gibt alles zurück, was Sie wollen, und überprüft die Variable, für die es verwendet wird, erneut this

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

       generate()
   }

Der Passwort - Generator wird jetzt rescoped wie thisund wir können daher eingestellt seed, hashund hashRepetitionsohne eine Variable. generate()gibt eine Instanz von zurück Password.

applyist ähnlich, aber es wird zurückkehren this:

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

Dies ist besonders nützlich als Ersatz für das Builder-Muster und wenn Sie bestimmte Konfigurationen wiederverwenden möchten.

let- Wird meistens verwendet, um Nullprüfungen zu vermeiden, kann aber auch als Ersatz für verwendet werden run. Der Unterschied besteht darin, dass dies thisimmer noch derselbe wie zuvor ist und Sie auf die Variable mit neuem Gültigkeitsbereich zugreifen, indem Sie it:

val fruitBasket = ...

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

Mit dem obigen Code wird der Apfel nur dann in den Warenkorb gelegt, wenn er nicht null ist. Beachten Sie auch, dass dies itjetzt nicht mehr optional ist, sodass Sie hier nicht auf eine NullPointerException stoßen (auch bekannt als "Sie müssen nicht verwenden, ?.um auf die Attribute zuzugreifen").

also- Verwenden Sie es, wenn Sie es verwenden möchten apply, aber nicht beschatten möchtenthis

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) = ...
}

Die Verwendung applyhier würde Schatten spenden this, sodass sich this.weightdies auf den Apfel und nicht auf den Obstkorb beziehen würde .


Hinweis: Ich habe die Beispiele schamlos aus meinem Blog übernommen

Lovis
quelle
1
Für jeden wie mich, der vom ersten Code überrascht ist, wird die letzte Zeile eines Lambda in Kotlin als Rückgabe angesehen.
Jay Lee
58

Es gibt noch ein paar Artikel wie hier , und hier lohnt es sich, einen Blick darauf zu werfen.

Ich denke, es liegt daran, wann Sie eine kürzere, präzisere innerhalb weniger Zeilen benötigen und um Verzweigungen oder die Überprüfung bedingter Anweisungen zu vermeiden (z. B. wenn nicht null, dann tun Sie dies).

Ich liebe dieses einfache Diagramm, deshalb habe ich es hier verlinkt. Sie können es daraus sehen, wie es von Sebastiano Gottardo geschrieben wurde.

Geben Sie hier die Bildbeschreibung ein

Bitte schauen Sie sich auch die Tabelle an, die meiner Erklärung unten beigefügt ist.

Konzept

Ich denke, es ist ein Rollenspiel innerhalb Ihres Codeblocks, wenn Sie diese Funktionen aufrufen + ob Sie sich selbst zurückhaben möchten (um Aufruffunktionen zu verketten oder um eine Ergebnisvariable zu setzen usw.).

Oben ist was ich denke.

Konzeptbeispiel

Sehen wir uns hier Beispiele für alle an

1.) myComputer.apply { }bedeutet, dass Sie als Hauptdarsteller auftreten möchten (Sie möchten glauben, dass Sie ein Computer sind), und dass Sie sich selbst zurückhaben möchten (Computer), damit Sie dies tun können

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

Ja, Sie selbst installieren einfach die Apps, stürzen sich ab und speichern sich als Referenz, damit andere etwas damit sehen und tun können.

2.) myComputer.also {}bedeutet, dass Sie völlig sicher sind , dass Sie kein Computer sind, dass Sie ein Außenseiter sind, der etwas damit anfangen möchte, und dass es auch ein Computer als zurückgegebenes Ergebnis sein soll.

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

3.) with(myComputer) { }bedeutet, dass Sie Hauptdarsteller (Computer) sind und sich dadurch nicht zurückhaben möchten.

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

4.) myComputer.run { }bedeutet, dass Sie Hauptdarsteller (Computer) sind und sich dadurch nicht zurückhaben möchten.

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

Aber es unterscheidet sich with { }in einem sehr subtilen Sinne davon, dass Sie Anrufe run { }wie folgt verketten können

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

Dies liegt an der run {}Erweiterungsfunktion, ist es aber with { }nicht. Sie rufen also auf run { }und thisinnerhalb des Codeblocks wird der Objekttyp des Aufrufers angezeigt. Sie können dies für eine hervorragende Erklärung für den Unterschied zwischen run {}und sehen with {}.

5.) myComputer.let { }bedeutet, dass Sie ein Außenseiter sind, der sich den Computer ansieht und etwas dagegen unternehmen möchte, ohne darauf zu achten, dass die Computerinstanz wieder an Sie zurückgegeben wird.

myComputer.let {
    myGrandpa.installVirusOn(it)
}

Die Art, es zu betrachten

Ich neige dazu, zu betrachten alsound letals etwas , das außerhalb extern ist. Wann immer Sie diese beiden Wörter sagen, ist es, als würden Sie versuchen, auf etwas zu reagieren. letInstallieren Sie den Virus auf diesem Computer und alsostürzen Sie ihn ab. Das hängt also davon ab, ob Sie Schauspieler sind oder nicht.

Für den Ergebnisteil ist es klar da. alsodrückt aus, dass es auch eine andere Sache ist, so dass Sie immer noch die Verfügbarkeit des Objekts selbst behalten. Somit wird es als Ergebnis zurückgegeben.

Alles andere ist damit verbunden this. Außerdem run/withinteressiert es eindeutig nicht, Objekt-Selbst zurück zu geben. Jetzt können Sie alle unterscheiden.

Ich denke manchmal, wenn wir uns von 100% programmier- / logikbasierten Beispielen entfernen, sind wir besser in der Lage, Dinge zu konzipieren. Aber das hängt richtig ab :)

haxpor
quelle
1
Das Diagramm sagt alles; Das beste bis jetzt.
Shukant Pal
7

Lassen Sie auch Apply, TakeIf, TakeUnless Erweiterungsfunktionen in Kotlin sein.

Um diese Funktion zu verstehen, müssen Sie die Erweiterungsfunktionen und Lambda-Funktionen in Kotlin verstehen .

Erweiterungsfunktion:

Durch die Verwendung der Erweiterungsfunktion können wir eine Funktion für eine Klasse erstellen, ohne eine Klasse zu erben.

Kotlin bietet ähnlich wie C # und Gosu die Möglichkeit, eine Klasse mit neuen Funktionen zu erweitern, ohne von der Klasse erben oder ein beliebiges Entwurfsmuster wie Decorator verwenden zu müssen. Dies erfolgt über spezielle Deklarationen, die als Erweiterungen bezeichnet werden. Kotlin unterstützt Erweiterungsfunktionen und Erweiterungseigenschaften.

Um herauszufinden, ob nur Zahlen in der enthalten sind String, können Sie eine Methode wie die folgende erstellen, ohne die StringKlasse zu erben .

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

Sie können die obige Erweiterungsfunktion wie folgt verwenden:

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

Das ist Drucke true.

Lambda-Funktionen:

Lambda-Funktionen sind wie Interface in Java. In Kotlin können Lambda-Funktionen jedoch als Parameter in Funktionen übergeben werden.

Beispiel:

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

Sie sehen, der Block ist eine Lambda-Funktion und wird als Parameter übergeben. Sie können die obige Funktion wie folgt verwenden:

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

Die obige Funktion wird wie folgt gedruckt:

Block executed
true

Ich hoffe, jetzt haben Sie eine Vorstellung von Erweiterungsfunktionen und Lambda-Funktionen. Jetzt können wir nacheinander zu den Erweiterungsfunktionen gehen.

Lassen

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

Zwei Typen T und R, die in der obigen Funktion verwendet werden.

T.let

Tkann ein beliebiges Objekt wie die String-Klasse sein. Sie können diese Funktion also mit beliebigen Objekten aufrufen.

block: (T) -> R

Im Parameter let sehen Sie die obige Lambda-Funktion. Außerdem wird das aufrufende Objekt als Parameter der Funktion übergeben. Sie können also das aufrufende Klassenobjekt innerhalb der Funktion verwenden. dann gibt es das R(ein anderes Objekt) zurück.

Beispiel:

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

Im obigen Beispiel nimmt let String als Parameter seiner Lambda-Funktion und gibt im Gegenzug Pair zurück.

Auf die gleiche Weise funktionieren andere Erweiterungsfunktionen.

ebenfalls

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

Die Erweiterungsfunktion verwendet alsodie aufrufende Klasse als Lambda-Funktionsparameter und gibt nichts zurück.

Beispiel:

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

anwenden

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

Wie auch, aber dasselbe aufrufende Objekt, das als Funktion übergeben wurde, sodass Sie die Funktionen und andere Eigenschaften verwenden können, ohne sie oder den Parameternamen aufzurufen.

Beispiel:

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

Im obigen Beispiel sehen Sie die Funktionen der String-Klasse, die direkt in der Lambda-Funktion aufgerufen werden.

takeIf

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

Beispiel:

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

Im obigen Beispiel numberwird nur eine Zeichenfolge angezeigt, die mit der phoneNumberübereinstimmt regex. Sonst wird es sein null.

takeUnless

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

Es ist das Gegenteil von takeIf.

Beispiel:

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

numberwird eine Zeichenfolge von phoneNumbernur haben, wenn nicht mit der übereinstimmt regex. Sonst wird es sein null.

Sie können ähnliche Antworten anzeigen, was hier nützlich ist. Unterschied zwischen kotlin auch, anwenden, lassen, verwenden, takeIf und takeUnless in Kotlin

Bhuvanesh BS
quelle
Sie haben einen Tippfehler in Ihrem letzten Beispiel, den Sie wahrscheinlich phoneNumber. takeUnless{}stattdessen gemeint haben phoneNumber. takeIf{}.
Ryan Amaral
1
Korrigiert. Vielen Dank an Ryan Amaral
Bhuvanesh BS
2

Nach meiner Erfahrung sollten Sie immer diejenige auswählen, bei der die geringste Menge an Code in das Lamda geschrieben werden muss, da es sich bei solchen Funktionen um syntaktischen Inline-Zucker ohne Leistungsunterschied handelt.

Bestimmen Sie dazu zunächst, ob das Lambda sein Ergebnis zurückgeben soll (wählen Sie run/ let) oder das Objekt selbst (wählen Sie apply/ also); In den meisten Fällen, wenn das Lambda ein einzelner Ausdruck ist, wählen Sie diejenigen mit demselben Blockfunktionstyp wie dieser Ausdruck aus, da ein Empfängerausdruck thisweggelassen werden kann und ein Parameterausdruck itkürzer ist als 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"

Wenn das Lambda jedoch aus einer Mischung von ihnen besteht, liegt es an Ihnen, dasjenige zu wählen, das besser in den Kontext passt oder mit dem Sie sich wohler fühlen.

Verwenden Sie auch diejenigen mit Parameterblockfunktion, wenn eine Dekonstruktion erforderlich ist:

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

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

Hier ist ein kurzer Vergleich all dieser Funktionen aus JetBrains offiziellem Kotlin-Kurs über Coursera Kotlin für Java-Entwickler : Differenztabelle Vereinfachte Implementierungen

Shreck Ye
quelle
2

Es gibt 6 verschiedene Scoping-Funktionen:

  1. T.run
  2. T.let
  3. T.apply
  4. Auch
  5. mit
  6. Lauf

Ich habe eine visuelle Notiz wie folgt vorbereitet, um die Unterschiede zu zeigen:

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

Geben Sie hier die Bildbeschreibung ein

Die Entscheidung hängt von Ihren Bedürfnissen ab. Die Anwendungsfälle verschiedener Funktionen überschneiden sich, sodass Sie die Funktionen basierend auf den spezifischen Konventionen auswählen können, die in Ihrem Projekt oder Team verwendet werden.

Obwohl die Bereichsfunktionen eine Möglichkeit sind, den Code präziser zu gestalten, vermeiden Sie eine Überbeanspruchung: Sie können die Lesbarkeit des Codes beeinträchtigen und zu Fehlern führen. Vermeiden Sie das Verschachteln von Bereichsfunktionen und seien Sie vorsichtig, wenn Sie sie verketten: Es ist leicht, sich über das aktuelle Kontextobjekt und den Wert dieses oder jenes zu verwirren.

Hier ist ein weiteres Diagramm für die Entscheidung, welches von https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84 verwendet werden soll Geben Sie hier die Bildbeschreibung ein

Einige Konventionen lauten wie folgt:

Verwenden Sie diese Option auch für zusätzliche Aktionen, die das Objekt nicht ändern, z. B. zum Protokollieren oder Drucken von Debug-Informationen.

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

Der häufigste Fall für die Anwendung ist die Objektkonfiguration.

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

Wenn Sie Schatten benötigen, verwenden Sie run

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

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

Wenn Sie das Empfängerobjekt selbst zurückgeben müssen, verwenden Sie apply oder auch

oiyio
quelle