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
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 this
i w związku z tym możemy ustawić seed
, hash
i hashRepetitions
bez użycia zmiennej.
generate()
zwróci wystąpienie Password
.
apply
jest 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 this
nadal 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 it
jest 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 apply
tutaj spowodowałoby cień this
, więc this.weight
odnosił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.
Pojęcie
Powyżej jest to, co myślę.
Przykład koncepcji
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ócvar 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łaniarun { }
w następujący sposóbmyComputer.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, alewith { }
tak nie jest. Więc wywołanierun { }
ithis
wewnątrz bloku kodu zostanie odzwierciedlone w typie obiektu wywołującego. Widać to na doskonałe wyjaśnienie różnicy pomiędzyrun {}
iwith {}
.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.Spojrzenie na to
Staram się patrzeć
also
ilet
jako 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ć.let
zainstaluj wirusa na tym komputerze ialso
wyłącz go. Więc to decyduje o tym, czy jesteś aktorem, czy nie.Jeśli chodzi o wynik, to wyraźnie tam jest.
also
wyraż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
. Dodatkoworun/with
wyraź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 :)
źródło
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.
Tak więc, aby znaleźć tylko liczby w
String
, możesz utworzyć metodę taką jak poniżej bez dziedziczeniaString
klasy.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
może być dowolnym obiektem, takim jak klasa String. więc możesz wywołać tę funkcję z dowolnymi obiektami.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
also
przyjmuje 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
number
będzie miał tylko ciąg znakówphoneNumber
zgodny zregex
. W przeciwnym razie tak będzienull
.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()) }
number
będzie miał ciągphoneNumber
tylko wtedy, gdy nie pasuje doregex
. W przeciwnym razie tak będzienull
.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
źródło
phoneNumber. takeUnless{}
zamiastphoneNumber. 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ż
źródło
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 (wybierzapply
/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,this
można je pominąć, gdy jest to wyrażenie parametryczne,it
jest 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 :
źródło