Я хочу иметь хороший пример для каждого запуска функции, пусть, применимо и с
Я прочитал эту статью, но все еще не нашел примера
Я хочу иметь хороший пример для каждого запуска функции, пусть, применимо и с
Я прочитал эту статью, но все еще не нашел примера
Ответы:
Все эти функции используются для переключения объема текущей функции / переменной. Они используются для хранения вещей, которые принадлежат друг другу, в одном месте (в основном инициализации).
Вот некоторые примеры:
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
это относится к яблоку, а не к корзине с фруктами.
Примечание: я бессовестно взял примеры из своего блога
Есть еще несколько статей , как здесь , и здесь , которые стоит посмотреть.
Я думаю, это связано с тем, что вам нужно более короткое, более лаконичное в нескольких строках и избежать ветвления или проверки условных операторов (например, если не 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% программировании / логике, мы оказываемся в лучшем положении для осмысления вещей. Но это зависит правильно :)
let, также, apply, takeIf, takeUnless - это функции расширения в Kotlin.
Чтобы понять эти функции, вы должны понимать функции расширения и лямбда-функции в Kotlin.
Функция расширения:
Используя функцию расширения, мы можем создать функцию для класса без наследования класса.
Kotlin, подобно C # и Gosu, предоставляет возможность расширять класс с помощью новых функций без необходимости наследовать от класса или использовать какой-либо тип шаблона проектирования, такой как Decorator. Это делается с помощью специальных объявлений, называемых расширениями. Kotlin поддерживает функции расширения и свойства расширения.
Итак, чтобы найти только числа в String
, вы можете создать метод, как показано ниже, без наследования String
class.
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{}
.
Есть 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 или также
По моему опыту, поскольку такие функции являются встроенным синтаксическим сахаром без разницы в производительности, вы всегда должны выбирать ту, которая требует написания наименьшего количества кода в 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 :