我希望每个函数run,let,apply,with
都有一个很好的例子我已阅读this article但仍缺少示例
答案 0 :(得分:70)
所有这些功能都用于切换当前功能/变量的范围。它们用于将属于一起的东西保存在一个地方(主要是初始化)。
以下是一些例子:
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
会引用苹果,而不会引用水果篮。
注意:我无耻地采用了例子from my blog
答案 1 :(得分:30)
我认为这取决于你需要更短,更简洁的几行,并避免分支或条件语句检查(如果不是null,那么这样做)。
我喜欢这个简单的图表,所以我在这里链接了它。你可以从Sebastiano Gottardo写的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
将反映到调用者类型的对象。您可以查看this,了解run {}
和with {}
之间差异的绝佳解释。
5。)myComputer.let { }
意味着你是局外人,看着电脑,想要对它做些什么,而不用担心计算机实例会再次返回给你。
myComputer.let {
myGrandpa.installVirusOn(it)
}
我倾向于将also
和let
看作外部的东西。每当你说出这两个词时,就像你试图采取行动一样。 let
在此计算机上安装病毒,also
使其崩溃。所以这就说明了你是否是一个演员。
对于结果部分,它显然在那里。 also
表示它也是另一回事,因此您仍然保留对象本身的可用性。因此它会返回它。
其他所有内容都与this
相关联。此外,run/with
显然对返回对象 - 自我回归并不感兴趣。现在你可以区分所有这些。
我认为有时当我们离开100%基于编程/逻辑的示例时,我们就能更好地概念化事物。但这取决于:)
答案 2 :(得分:8)
有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
}
如果您需要返回接收者对象本身,请使用应用或也
答案 3 :(得分:5)
让,也,申请,takeIf,takeUnless 是Kotlin的扩展功能。
要了解这些功能,您必须了解Kotlin中的扩展功能和 Lambda功能。
扩展功能:
通过使用扩展函数,我们可以在不继承类的情况下为类创建函数。
Kotlin,类似于C#和Gosu,提供了扩展课程的能力 具有新功能而无需从类继承或使用 任何类型的设计模式,如装饰师。这是通过特殊方式完成的 声明称为扩展。 Kotlin支持扩展功能 和扩展属性。
因此,要查找String
中是否只有数字,您可以在不继承String
类的情况下创建如下方法。
fun String.isNumber(): Boolean = this.matches("[0-9]+".toRegex())
你可以使用上面的扩展功能,
val phoneNumber = "8899665544"
println(phoneNumber.isNumber)
是打印true
。
Lambda函数:
Lambda函数就像Java中的Interface。但是在Kotlin中,lambda函数可以作为函数中的参数传递。
示例:
fun String.isNumber(block: () -> Unit): Boolean {
return if (this.matches("[0-9]+".toRegex())) {
block()
true
} else false
}
你可以看到,该块是一个lambda函数,它作为参数传递。你可以使用上面这样的函数,
val phoneNumber = "8899665544"
println(phoneNumber.isNumber {
println("Block executed")
})
上面的功能会像这样打印,
Block executed
true
我希望,现在你对扩展函数和Lambda函数有所了解。现在我们可以逐个转到扩展功能。
可让强>
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
上述功能中使用了两种类型T和R.
T.let
T
可以是String类之类的任何对象。所以你可以用任何对象调用这个函数。
block: (T) -> R
在let的参数中,您可以看到上面的lambda函数。此外,调用对象作为函数的参数传递。因此,您可以在函数内部使用调用类对象。然后它返回R
(另一个对象)。
示例:
val phoneNumber = "8899665544"
val numberAndCount: Pair<Int, Int> = phoneNumber.let { it.toInt() to it.count() }
在上面的示例中,我们将 String 作为其lambda函数的参数,并返回 Pair 。
以同样的方式,其他扩展功能也可以。
同时强>
public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
扩展函数also
将调用类作为lambda函数参数,不返回任何内容。
示例:
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)
}
您可以在上面的示例中看到在lambda函数中直接调用的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
字符串。否则,它将是cvc-complex-type.2.4.a: Invalid content was found starting with element 'constructor-args'. One of '{"http:// www.springframework.org/schema/beans":import, "http://www.springframework.org/schema/beans":alias, "http://www.springframework.org/schema/beans":bean, WC[##other:"http://www.springframework.org/ schema/beans"], "http://www.springframework.org/schema/beans":beans}' is expected.
。
您可以在difference between kotlin also, apply, let, use, takeIf and takeUnless in Kotlin
查看类似的答案答案 4 :(得分:0)
根据我的经验,由于此类函数是内联语法糖,没有性能差异,因此,您应始终选择需要在lamda中编写最少代码的函数。
为此,首先确定您是要lambda返回其结果(选择run
/ let
)还是对象本身(选择apply
/ also
);那么在大多数情况下,当lambda是单个表达式时,请选择与该表达式具有相同的块函数类型的表达式,因为当它是接收方表达式时,this
可以省略,而当它是参数表达式{{1 }}比it
短:
this
但是,当lambda由它们的混合体组成时,则取决于您,然后选择一个更适合上下文或您更喜欢的lambda。
另外,在需要解构时,请使用带有参数块功能的参数:
val object: Type = ...
fun Type.receiverFunction(...): ReturnType { ... }
object.run/*apply*/ { receiverFunction(...) } // shorter because "this" can be omitted
object.let/*also*/ { it.receiverFunction(...) } // longer
fun parameterFunction(parameter: Type, ...): ReturnType { ... }
pair.run/*apply*/ { parameterFunction(this, ...) } // longer
pair.let/*also*/ { parameterFunction(it, ...) } // shorter because "it" is shorter than "this"
以下是JetBrains关于Coursera Kotlin for Java Developers的Kotlin官方课程中所有这些功能的简要比较: