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
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
Antworten:
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
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.
Bitte schauen Sie sich auch die Tabelle an, die meiner Erklärung unten beigefügt ist.
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.
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)
}
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 :)
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
phoneNumber. takeUnless{}stattdessen gemeint haben phoneNumber. takeIf{}.
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 :

Es gibt 6 verschiedene Scoping-Funktionen:
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)
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

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