Приклад того, коли слід використовувати run, let, apply, також і with на Kotlin


100

Я хотів би мати хороший приклад для кожного запуску функції, дозвольте, застосуйте, також, з

Я прочитав цю статтю, але досі не маю прикладу

Відповіді:


121

Всі ці функції використовуються для перемикання області дії поточної функції / змінної. Вони використовуються для зберігання речей, що належать разом, в одному місці (переважно ініціалізації).

Ось кілька прикладів:

run - повертає все, що ви хочете, і переорієнтує змінну, до якої вона використана this

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

       generate()
   }

Генератор паролів тепер реконструюється як thisі, отже seed, ми можемо встановити , hashі hashRepetitionsбез використання змінної. generate()поверне екземпляр Password.

applyподібний, але він поверне this:

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

Це особливо корисно як заміна шаблону Builder, і якщо ви хочете повторно використовувати певні конфігурації.

let- здебільшого використовується для уникнення нульових перевірок, але може також використовуватися як заміна run. Різниця полягає в тому, що це thisбуде все те ж саме, що і раніше, і ви отримуєте доступ до змінної масштабу, використовуючи it:

val fruitBasket = ...

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

Наведений вище код додасть яблуко в кошик, лише якщо воно не є нульовим. Також зауважте, що itтепер це не є необов’язковим, тому ви тут не зіткнетеся з NullPointerException (він же вам не потрібно використовувати ?.для доступу до його атрибутів)

also- використовуйте його, коли хочете використовувати apply, але не хочете затінюватиthis

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

Використання applyтут означало б тінь this, тому це this.weightстосувалося б яблука, а не кошика з фруктами.


Примітка. Я безсоромно брав приклади зі свого блогу


2
Для таких, як я, здивованих першим кодом, останній рядок лямбди вважається значенням повернення в Kotlin.
Джей Лі

62

Є ще кілька таких статей, як тут , і тут варто поглянути.

Я думаю, що це залежить від того, коли вам потрібен коротший, стислий за кілька рядків, і щоб уникнути розгалуження або умовної перевірки висловлень (наприклад, якщо не null, то зробіть це).

Мені подобається ця проста діаграма, тому я пов’язав її тут. Це видно з цього, як написав Себастьяно Готтардо.

введіть тут опис зображення

Будь ласка, також перегляньте таблицю, що супроводжує моє пояснення нижче.

Концепція

Я думаю, що це як рольовий спосіб всередині блоку коду, коли ви викликаєте ці функції + чи хочете ви повернути себе (зв'язати функції виклику, або встановити як змінну результату тощо).

Вище - це те, що я думаю.

Приклад концепції

Давайте подивимося приклади для всіх них тут

1.) myComputer.apply { }означає, що ви хочете виступити головною дійовою особою (ви хочете думати, що ви комп’ютер), і ви хочете повернути себе (комп’ютер), щоб ви могли робити

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

Так, ви самі просто встановлюєте додатки, збиваєтесь і зберігаєте себе як посилання, щоб дозволити іншим бачити та робити щось із цим.

2.) myComputer.also {}означає, що ви повністю впевнені , що не є комп’ютером, ви сторонній особа, яка хоче щось з цим зробити, а також хоче, щоб комп’ютер був повернутим результатом.

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

3.) with(myComputer) { }означає, що ви головний актор (комп’ютер), і ви не хочете, щоб в результаті ви повернулися.

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

4.) myComputer.run { }означає, що ви головний актор (комп'ютер), і ви не хочете, щоб в результаті ви повернулися.

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

але це відрізняється від with { }дуже тонкого сенсу того, що ви можете ланцюговий дзвінок, run { }як показано нижче

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

Це пов’язано з run {}функцією розширення, але with { }ні. Таким чином, ви зателефонуєте, run { }і thisвсередині блоку коду буде відображено тип об'єкта, що викликає. Ви можете побачити це для чудового пояснення різниці між run {}і with {}.

5.) myComputer.let { }означає, що ви сторонній, хто дивиться на комп’ютер і хочете щось з цим зробити, не дбаючи про те, щоб екземпляр комп’ютера повертався вам знову.

myComputer.let {
    myGrandpa.installVirusOn(it)
}

Спосіб на це подивитися

Я схильний дивитись alsoі letяк на щось зовнішнє, зовні. Кожного разу, коли ви вимовляєте ці два слова, це все одно, що ви намагаєтеся щось реагувати. letвстановіть вірус на цей комп’ютер і alsoрозбийте його. Отже, це привертає увагу до того, актор ти чи ні.

Для результату це явно є. alsoвисловлює, що це також інша справа, тому ви все ще зберігаєте доступність самого об'єкта. Таким чином він повертає це як результат.

Все інше асоціюється з this. До того ж run/withявно не цікавить повернення об'єкта-себе назад. Тепер ви можете диференціювати їх усіх.

Я думаю, що іноді, коли ми відходимо від 100% програмування / логіки, що базується на прикладах, тоді ми маємо кращу позицію для концептуалізації речей. Але це залежить правильно :)


1
Діаграма все це розповідає; найкраще на даний момент.
Шукант Пал

Це має бути прийнятою та найбільш голосованою відповіддю
Сегун Вахааб

8

дозвольте, також застосувати, takeIf, takeUnless, якщо функції розширення в Kotlin.

Щоб зрозуміти ці функції, ви повинні розуміти функції Extension та Lambda у Kotlin.

Функція розширення:

Використовуючи функцію розширення, ми можемо створити функцію для класу без успадкування класу.

Kotlin, подібно до C # та Gosu, надає можливість розширити клас новою функціональністю без необхідності успадковувати від класу або використовувати будь-який тип шаблону дизайну, такий як Decorator. Це робиться за допомогою спеціальних оголошень, які називаються розширеннями. Kotlin підтримує функції розширення та властивості розширення.

Отже, щоб знайти лише цифри в String, ви можете створити метод, як показано нижче, не успадковуючи Stringклас.

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

Ви можете використовувати наведену вище функцію розширення, як це,

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

що це відбитки true.

Лямбда-функції:

Лямбда-функції подібні до інтерфейсу в Java. Але в Котліні лямбда-функції можуть передаватися як параметр у функціях.

Приклад:

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

Як бачите, блок є лямбда-функцією і передається як параметр. Ви можете використовувати наведену вище функцію таким чином,

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

Вищевказана функція друкуватиметься так,

Block executed
true

Сподіваюсь, тепер ви отримали уявлення про функції розширення та функції Лямбда. Тепер ми можемо перейти до функцій розширення по черзі.

дозволяє

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

Два типи T і R, використані у вищезазначеній функції.

T.let

Tможе бути будь-яким об’єктом, як клас String. так що ви можете викликати цю функцію з будь-якими об'єктами.

block: (T) -> R

У параметрі let ви можете побачити вищезазначену лямбда-функцію. Також об'єкт, що викликає, передається як параметр функції. Таким чином, ви можете використовувати об'єкт класу, що викликає всередині функції. потім він повертає R(інший об'єкт).

Приклад:

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

У наведеному вище прикладі let приймає String як параметр своєї лямбда-функції, і він повертає Pair у відповідь.

Таким же чином працює і інша функція розширення.

також

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

Функція розширення alsoприймає клас виклику як параметр лямбда-функції і нічого не повертає.

Приклад:

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

подати заявку

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

Те саме, що і також, але той самий об'єкт, що викликається, переданий як функція, тому ви можете використовувати функції та інші властивості, не викликаючи його або ім'я параметра.

Приклад:

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

Ви можете побачити у наведеному вище прикладі функції класу String, що безпосередньо викликаються всередині лямбда-функції.

takeIf

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

Приклад:

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

У наведеному вище прикладі numberбуде мати рядок, phoneNumberлише він відповідає regex. Інакше буде null.

takeUnless

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

Це зворотна сторона takeIf.

Приклад:

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

numberбуде мати рядок phoneNumberлише якщо не відповідає regex. Інакше буде null.

Ви можете переглянути подібні відповіді, що є корисною тут, різниця між kotlin також, застосовувати, дозволяти, використовувати, takeIf та takeUnless у Kotlin


У вашому останньому прикладі у вас є помилка, яку ви, мабуть, мали на увазі phoneNumber. takeUnless{}замість phoneNumber. takeIf{}.
Ryan Amaral

1
Виправлено. Дякую @Ryan Amaral
Bhuvanesh BS

5

Існує 6 різних функцій масштабування:

  1. T.run
  2. Т. нехай
  3. Т. застосовувати
  4. Т. також
  5. з
  6. бігти

Я підготував наочну нотатку, як показано нижче, щоб показати відмінності:

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

введіть тут опис зображення

Рішення залежить від ваших потреб. Варіанти використання різних функцій перекриваються, так що ви можете вибирати функції, виходячи з конкретних умов, що використовуються у вашому проекті або команді.

Хоча функції області дії - це спосіб зробити код більш стислим, уникайте надмірного їх використання: це може зменшити читабельність коду та призвести до помилок. Уникайте вкладених функцій області та будьте обережні при їх ланцюжковому налаштуванні: легко заплутатися щодо поточного контекстного об’єкта та значення цього чи цього.

Ось ще одна схема для вибору, яку використовувати з https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84 введіть тут опис зображення

Деякі конвенції є такими:

Використовуйте також для додаткових дій, які не змінюють об'єкт, таких як реєстрація або друк інформації про налагодження.

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

Типовим випадком застосування є конфігурація об'єкта.

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

Якщо вам потрібна тінь, використовуйте run

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

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

Якщо вам потрібно повернути сам об'єкт приймача, використовуйте apply або також


3

Згідно з моїм досвідом, оскільки такими функціями є вбудований синтаксичний цукор без різниці в продуктивності, ви завжди повинні вибирати ту, яка вимагає введення найменшої кількості коду в lamda.

Для цього спочатку визначтесь, чи хочете ви, щоб лямбда повертала свій результат (select run/ let), або сам об’єкт (select apply/ also); тоді в більшості випадків, коли лямбда є єдиним виразом, вибирайте ті, що мають той самий тип функції блоку, що і цей вираз, тому що коли це вираз одержувача, thisйого можна опустити, коли це вираз параметра, itкоротший за 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"

Однак, коли лямбда складається з їх поєднання, тоді вам залишається вибрати той, який краще вписується в контекст, або ви відчуваєте себе більш комфортно.

Також використовуйте ті з функцією блоку параметрів, коли потрібна деконструкція:

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

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

Ось коротке порівняння всіх цих функцій з офіційного курсу JetBrains Kotlin на Coursera Kotlin для розробників Java : Таблиця різниць Спрощені реалізації

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.