Подтвердить что ты не робот

Пример того, когда мы должны использовать run, let, apply, also и with на Kotlin

Я хочу иметь хороший пример для каждой функции run, let, apply, также, с

Я прочитал эту статью, но все еще отсутствует пример

4b9b3361

Ответ 1

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

Вот несколько примеров:

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

Есть еще несколько статей, таких как здесь, и здесь, которые стоит взглянуть.

Я думаю, что вам не нужно, чтобы вам было короче, более кратким в нескольких строках, и чтобы избежать проверки ветвления или условного оператора (например, если не null, тогда сделайте это).

Мне нравится эта простая диаграмма, поэтому я связал ее здесь. Вы можете видеть это из this, как написано Себастьяно Готтардо.

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

Пожалуйста, посмотрите также диаграмму, сопровождающую мое объяснение ниже.

Концепция

Я думаю, что это будет играть роль в вашем блоке кода, когда вы вызываете эти функции +, хотите ли вы вернуться назад (чтобы связать функции вызова или установить переменную результата и т.д.).

Выше я думаю.

Пример концепции

Посмотрите примеры для всех из них здесь

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% программирующих/логических примеров, тогда мы находимся в лучшем положении для концептуализации вещей. Но это зависит от права:)

Ответ 3

пусть также, apply, takeIf, takeUnless являются функциями расширения в Kotlin.

Чтобы понять эти функции, вы должны понимать функции расширения и лямбда-функции в Kotlin.

Функция расширения:

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

Kotlin, похожий на С# и Gosu, предоставляет возможность расширять класс с новой функциональностью без необходимости наследования от класса или использования любой тип шаблона дизайна, например декоратор. Это делается с помощью специальных объявления называются расширениями. Kotlin поддерживает функции расширения и свойства расширения.

Таким образом, чтобы найти только числа в String, вы можете создать метод, подобный приведенному ниже, не наследуя класс String.

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

вы можете использовать вышеуказанную функцию расширения следующим образом,

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

который печатает true.

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

Лямбда-функции аналогичны интерфейсу в Java. Но в Kotlin лямбда-функции могут быть переданы как параметр в функции.

Пример:

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 в качестве параметра своей лямбда-функции и возвращает пару в ответ.

Точно так же работает другая функция расширения.

также

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

Это обратное восприятие.

Пример:

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

number будет содержать строку phoneNumber, только если она не совпадает с regex. В противном случае это будет null.

Вы можете просмотреть похожие ответы, которые здесь полезны Разница между kotlin также: apply, let, use, takeIf и takeUnless в Kotlin

Ответ 4

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

Для этого сначала определите, хотите ли вы, чтобы лямбда-функция возвращала свой результат (выберите run/let) или сам объект (выберите 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: Difference table Simplified implementations