Przykład kiedy powinniśmy używać run, let, apply, also and with on Kotlin

108

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

UmAnusorn
źródło

Odpowiedzi:

129

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

Lovis
źródło
6
Dla każdego takiego jak ja, zaskoczonego pierwszym kodem, ostatni wiersz lambdy jest traktowany jako wartość zwrotna w Kotlinie.
Jay Lee,
66

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.

wprowadź opis obrazu tutaj

Proszę również spojrzeć na tabelę towarzyszącą mojemu wyjaśnieniu poniżej.

Pojęcie

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ę.

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ó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)
}

Spojrzenie na to

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 :)

haxpor
źródło
1
Diagram mówi wszystko; Najlepsze do tej pory.
Shukant Pal
8

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

Bhuvanesh BS
źródło
Masz literówkę w ostatnim przykładzie, prawdopodobnie miałeś na myśli phoneNumber. takeUnless{}zamiast phoneNumber. takeIf{}.
Ryan Amaral
1
Poprawione. Dzięki @Ryan Amaral
Bhuvanesh BS
8

Istnieje 6 różnych funkcji określania zakresu:

  1. T.run
  2. T.let
  3. T.apply
  4. T.also
  5. z
  6. biegać

Przygotowałem wizualną notatkę, jak poniżej, aby pokazać różnice:

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

wprowadź opis obrazu tutaj

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 wprowadź opis obrazu tutaj

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ż

oiyio
źródło
4

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 : Tabela różnic Uproszczone wdrożenia

Shreck Ye
źródło