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

101

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

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

UmAnusorn
источник

Ответы:

122

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

Вот некоторые примеры:

run - возвращает все, что вы хотите, и изменяет область видимости переменной, для которой он используется this

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

       generate()
   }

Генератор паролей теперь rescoped , как 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это относится к яблоку, а не к корзине с фруктами.


Примечание: я бессовестно взял примеры из своего блога

Ловис
источник
3
Для таких, как я, удивленных первым кодом, последняя строка лямбды считается в Котлине возвращаемой величиной.
Джей Ли
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

let, также, apply, takeIf, takeUnless - это функции расширения в Kotlin.

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

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

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

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

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

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

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

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

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

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

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

Пример:

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 }

функция extension 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 также, apply, let, use, takeIf и takeUnless в Kotlin

Бхуванеш Б.С.
источник
В последнем примере вы допустили опечатку, возможно, вы имели в виду phoneNumber. takeUnless{}вместо phoneNumber. takeIf{}.
Райан Амарал
1
Исправленный. Спасибо @Ryan Amaral
Bhuvanesh BS
5

Есть 6 различных функций обзора:

  1. T.run
  2. T.let
  3. T.apply
  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 или также

Oiyio
источник
3

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

Для этого сначала определите, хотите ли вы, чтобы лямбда возвращала свой результат (выберите 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 : Таблица различий Упрощенные реализации

Шрек Йе
источник