Я хочу иметь хороший пример для каждой функции run, let, apply, также, с
Я прочитал эту статью, но все еще отсутствует пример
Я хочу иметь хороший пример для каждой функции run, let, apply, также, с
Я прочитал эту статью, но все еще отсутствует пример
Все эти функции используются для переключения области действия текущей функции/переменной. Они используются, чтобы хранить вещи, которые принадлежат друг другу, в одном месте (в основном инициализации).
Вот несколько примеров:
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
будет ссылаться на яблоко, а не на корзину с фруктами.
Примечание: я бесстыдно взял примеры из своего блога
Есть еще несколько статей, таких как здесь, и здесь, которые стоит взглянуть.
Я думаю, что вам не нужно, чтобы вам было короче, более кратким в нескольких строках, и чтобы избежать проверки ветвления или условного оператора (например, если не 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% программирующих/логических примеров, тогда мы находимся в лучшем положении для концептуализации вещей. Но это зависит от права:)
пусть также, 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
Согласно моему опыту, поскольку такие функции являются встроенным синтаксическим сахаром без различий в производительности, вы всегда должны выбирать ту, которая требует написания наименьшего количества кода в лямде.
Для этого сначала определите, хотите ли вы, чтобы лямбда-функция возвращала свой результат (выберите 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: