Chciałbym mieć dobry przykład dla każdej funkcji uruchomionej, niech, zastosuj, również z
Przeczytałem ten artykuł, ale wciąż brakuje mi przykładu
Chciałbym mieć dobry przykład dla każdej funkcji uruchomionej, niech, zastosuj, również z
Przeczytałem ten artykuł, ale wciąż brakuje mi przykładu
Odpowiedzi:
Wszystkie te funkcje służą do przełączania zakresu bieżącej funkcji / zmiennej. Służą do przechowywania rzeczy, które należą do siebie w jednym miejscu (głównie inicjalizacje).
Oto kilka przykładów:
run - zwraca wszystko, co chcesz i ponownie określa zakres zmiennej, na której jest używany this
val password: Password = PasswordGenerator().run {
seed = "someString"
hash = {s -> someHash(s)}
hashRepetitions = 1000
generate()
}
Generator haseł jest teraz rescoped jak thisi w związku z tym możemy ustawić seed, hashi hashRepetitionsbez użycia zmiennej.
generate()zwróci wystąpienie Password.
applyjest podobny, ale zwróci this:
val generator = PasswordGenerator().apply {
seed = "someString"
hash = {s -> someHash(s)}
hashRepetitions = 1000
}
val pasword = generator.generate()
Jest to szczególnie przydatne jako zamiennik wzorca Builder i jeśli chcesz ponownie użyć niektórych konfiguracji.
let- używany głównie w celu uniknięcia sprawdzeń zerowych, ale może być również używany jako zamiennik run. Różnica polega na tym, że thisnadal będzie taki sam jak poprzednio, a dostęp do zmiennej o zmienionym zakresie uzyskasz za pomocą it:
val fruitBasket = ...
apple?.let {
println("adding a ${it.color} apple!")
fruitBasket.add(it)
}
Powyższy kod doda jabłko do koszyka tylko wtedy, gdy nie jest puste. Zauważ również, że nie itjest już opcjonalne, więc nie napotkasz tutaj wyjątku NullPointerException (aka. Nie musisz go używać, ?.aby uzyskać dostęp do jego atrybutów)
also- używaj go, gdy chcesz użyć apply, ale nie chcesz cieniathis
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) = ...
}
Użycie applytutaj spowodowałoby cień this, więc this.weightodnosiło się to do jabłka, a nie do koszyka z owocami.
Uwaga: bezwstydnie wziąłem przykłady z mojego bloga
Jest jeszcze kilka artykułów takich jak tutaj , a tutaj warto się przyjrzeć.
Myślę, że sprowadza się to do sytuacji, gdy potrzebujesz krótszego, bardziej zwięzłego w kilku wierszach i aby uniknąć rozgałęziania lub sprawdzania instrukcji warunkowych (na przykład jeśli nie null, zrób to).
Uwielbiam ten prosty wykres, więc połączyłem go tutaj. Widać to z tego, co napisał Sebastiano Gottardo.
Proszę również spojrzeć na tabelę towarzyszącą mojemu wyjaśnieniu poniżej.
Myślę, że jest to odgrywanie ról wewnątrz bloku kodu, gdy wywołujesz te funkcje + czy chcesz siebie z powrotem (aby połączyć funkcje wywołania, ustawić zmienną wynikową itp.).
Powyżej jest to, co myślę.
Zobaczmy tutaj przykłady ich wszystkich
1) myComputer.apply { }oznacza, że chcesz grać jako główny aktor (chcesz myśleć, że jesteś komputerem) i chcesz siebie z powrotem (komputer), aby móc
var crashedComputer = myComputer.apply {
// you're the computer, you yourself install the apps
// note: installFancyApps is one of methods of computer
installFancyApps()
}.crash()
Tak, sam po prostu instalujesz aplikacje, wyłączasz się i zapisujesz siebie jako odniesienie, aby inni mogli zobaczyć i coś z tym zrobić.
2.) myComputer.also {}oznacza, że jesteś całkowicie pewien, że nie jesteś komputerem, jesteś osobą z zewnątrz, która chce coś z tym zrobić, a także chce, aby komputer był zwracany.
var crashedComputer = myComputer.also {
// now your grandpa does something with it
myGrandpa.installVirusOn(it)
}.crash()
3.) with(myComputer) { }oznacza, że jesteś głównym aktorem (komputerem) i nie chcesz, aby w rezultacie wróciłeś.
with(myComputer) {
// you're the computer, you yourself install the apps
installFancyApps()
}
4.) myComputer.run { }oznacza, że jesteś głównym aktorem (komputerem) i nie chcesz, aby w rezultacie wróciłeś.
myComputer.run {
// you're the computer, you yourself install the apps
installFancyApps()
}
ale różni się od with { }w bardzo subtelnym sensie, że możesz łączyć wywołania run { }w następujący sposób
myComputer.run {
installFancyApps()
}.run {
// computer object isn't passed through here. So you cannot call installFancyApps() here again.
println("woop!")
}
Wynika to z run {}funkcji rozszerzenia, ale with { }tak nie jest. Więc wywołanie run { }i thiswewnątrz bloku kodu zostanie odzwierciedlone w typie obiektu wywołującego. Widać to na doskonałe wyjaśnienie różnicy pomiędzy run {}i with {}.
5.) myComputer.let { }oznacza, że jesteś osobą z zewnątrz, która patrzy na komputer i chcesz coś z tym zrobić, nie dbając o to, aby instancja komputera została ponownie zwrócona.
myComputer.let {
myGrandpa.installVirusOn(it)
}
Staram się patrzeć alsoi letjako coś, co jest zewnętrzne, na zewnątrz. Za każdym razem, gdy mówisz te dwa słowa, to tak, jakbyś próbował coś zrobić. letzainstaluj wirusa na tym komputerze i alsowyłącz go. Więc to decyduje o tym, czy jesteś aktorem, czy nie.
Jeśli chodzi o wynik, to wyraźnie tam jest. alsowyraża, że to także inna sprawa, więc nadal zachowujesz dostępność samego obiektu. Dlatego zwraca go jako wynik.
Wszystko inne jest związane z this. Dodatkowo run/withwyraźnie nie jest zainteresowany zwrotem siebie obiektu. Teraz możesz je wszystkie rozróżnić.
Myślę, że czasami, kiedy odejdziemy od przykładów opartych w 100% na programowaniu / logice, jesteśmy w lepszej pozycji do konceptualizacji rzeczy. Ale to zależy dobrze :)
let, także, apply, takeIf, takeUnless są funkcjami rozszerzającymi w Kotlinie.
Aby zrozumieć te funkcje, musisz zrozumieć funkcje rozszerzeń i funkcje Lambda w Kotlinie.
Funkcja rozszerzenia:
Korzystając z funkcji rozszerzającej, możemy stworzyć funkcję dla klasy bez dziedziczenia klasy.
Kotlin, podobnie jak C # i Gosu, zapewnia możliwość rozszerzenia klasy o nową funkcjonalność bez konieczności dziedziczenia z klasy lub używania jakiegokolwiek wzorca projektowego, takiego jak Decorator. Odbywa się to poprzez specjalne deklaracje zwane rozszerzeniami. Kotlin obsługuje funkcje rozszerzeń i właściwości rozszerzeń.
Tak więc, aby znaleźć tylko liczby w String, możesz utworzyć metodę taką jak poniżej bez dziedziczenia Stringklasy.
fun String.isNumber(): Boolean = this.matches("[0-9]+".toRegex())
możesz użyć powyższej funkcji rozszerzenia w ten sposób,
val phoneNumber = "8899665544"
println(phoneNumber.isNumber)
czyli drukuje true.
Funkcje lambda:
Funkcje lambda są podobne do interfejsu w Javie. Ale w Kotlinie funkcje lambda mogą być przekazywane jako parametr w funkcjach.
Przykład:
fun String.isNumber(block: () -> Unit): Boolean {
return if (this.matches("[0-9]+".toRegex())) {
block()
true
} else false
}
Jak widać, blok jest funkcją lambda i jest przekazywany jako parametr. Możesz użyć powyższej funkcji w ten sposób,
val phoneNumber = "8899665544"
println(phoneNumber.isNumber {
println("Block executed")
})
Powyższa funkcja wydrukuje się w ten sposób,
Block executed
true
Mam nadzieję, że teraz masz pomysł na funkcje rozszerzeń i funkcje Lambda. Teraz możemy przejść do funkcji rozszerzających jeden po drugim.
pozwolić
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
W powyższej funkcji zastosowano dwa typy T i R.
T.let
Tmoże być dowolnym obiektem, takim jak klasa String. więc możesz wywołać tę funkcję z dowolnymi obiektami.
block: (T) -> R
W parametrze let możesz zobaczyć powyższą funkcję lambda. Ponadto obiekt wywołujący jest przekazywany jako parametr funkcji. Możesz więc użyć wywołującego obiektu klasy wewnątrz funkcji. następnie zwraca R(inny obiekt).
Przykład:
val phoneNumber = "8899665544"
val numberAndCount: Pair<Int, Int> = phoneNumber.let { it.toInt() to it.count() }
W powyższym przykładzie jako parametr swojej funkcji lambda przyjmuje ciąg znaków i zwraca w zamian Pair .
W ten sam sposób działa inna funkcja rozszerzająca.
również
public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
funkcja rozszerzenia alsoprzyjmuje klasę wywołującą jako parametr funkcji lambda i nic nie zwraca.
Przykład:
val phoneNumber = "8899665544"
phoneNumber.also { number ->
println(number.contains("8"))
println(number.length)
}
zastosować
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
To samo co również, ale ten sam obiekt wywołujący przekazany jako funkcja, dzięki czemu można używać funkcji i innych właściwości bez wywoływania go lub nazwy parametru.
Przykład:
val phoneNumber = "8899665544"
phoneNumber.apply {
println(contains("8"))
println(length)
}
W powyższym przykładzie możesz zobaczyć funkcje klasy String wywoływane bezpośrednio wewnątrz funkcji lambda.
TakeIf
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null
Przykład:
val phoneNumber = "8899665544"
val number = phoneNumber.takeIf { it.matches("[0-9]+".toRegex()) }
W powyższym przykładzie numberbędzie miał tylko ciąg znaków phoneNumberzgodny z regex. W przeciwnym razie tak będzie null.
TakeUnless
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null
To jest odwrotność takeIf.
Przykład:
val phoneNumber = "8899665544"
val number = phoneNumber.takeUnless { it.matches("[0-9]+".toRegex()) }
numberbędzie miał ciąg phoneNumbertylko wtedy, gdy nie pasuje do regex. W przeciwnym razie tak będzie null.
Możesz zobaczyć podobne odpowiedzi, które są tutaj przydatne, różnica między kotlinem również, aplikuj, pozwól, używaj, bierzIf i przyjmujUnless w Kotlinie
phoneNumber. takeUnless{}zamiast phoneNumber. takeIf{}.
Istnieje 6 różnych funkcji określania zakresu:
Przygotowałem wizualną notatkę, jak poniżej, aby pokazać różnice:
data class Citizen(var name: String, var age: Int, var residence: String)
Decyzja zależy od Twoich potrzeb. Przypadki użycia różnych funkcji nakładają się na siebie, dzięki czemu można wybrać funkcje w oparciu o określone konwencje używane w projekcie lub zespole.
Chociaż funkcje zakresu są sposobem na uczynienie kodu bardziej zwięzłym, unikaj ich nadużywania: może to zmniejszyć czytelność kodu i prowadzić do błędów. Unikaj zagnieżdżania funkcji zakresu i zachowaj ostrożność podczas łączenia ich w łańcuch: łatwo jest się pomylić co do bieżącego obiektu kontekstu i wartości tego lub tego.
Oto kolejny diagram służący do decydowania, którego użyć z https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84

Niektóre konwencje są następujące:
Służy również do dodatkowych działań, które nie zmieniają obiektu, takich jak rejestrowanie lub drukowanie informacji debugowania.
val numbers = mutableListOf("one", "two", "three")
numbers
.also { println("The list elements before adding new one: $it") }
.add("four")
Typowym przypadkiem zastosowania jest konfiguracja obiektu.
val adam = Person("Adam").apply {
age = 32
city = "London"
}
println(adam)
Jeśli potrzebujesz cieniowania, użyj run
fun test() {
var mood = "I am sad"
run {
val mood = "I am happy"
println(mood) // I am happy
}
println(mood) // I am sad
}
Jeśli chcesz zwrócić sam obiekt odbiorcy, użyj zastosowania lub też
Z mojego doświadczenia wynika, że ponieważ takie funkcje są wbudowanym cukrem składniowym bez różnicy w wydajności, należy zawsze wybierać tę, która wymaga napisania najmniejszej ilości kodu w lamdzie.
Aby to zrobić, najpierw określ, czy chcesz, aby lambda zwracała swój wynik (wybierz run/ let), czy sam obiekt (wybierz apply/ also); wtedy w większości przypadków, gdy lambda jest pojedynczym wyrażeniem, wybierz te z tym samym typem funkcji bloku, co to wyrażenie, ponieważ gdy jest to wyrażenie odbierające, thismożna je pominąć, gdy jest to wyrażenie parametryczne, itjest krótsze niż 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"
Jeśli jednak lambda składa się z ich kombinacji, to do Ciebie należy wybór tego, który będzie lepiej pasował do kontekstu lub będzie dla Ciebie wygodniejszy.
Jeśli potrzebna jest dekonstrukcja, użyj również tych z funkcją blokowania parametrów:
val pair: Pair<TypeA, TypeB> = ...
pair.run/*apply*/ {
val (first, second) = this
...
} // longer
pair.let/*also*/ { (first, second) -> ... } // shorter
Oto krótkie porównanie wszystkich tych funkcji z oficjalnego kursu JetBrains Kotlin na Coursera Kotlin dla programistów Java :
