我们何时应该在Kotlin上使用run、let、apply、also和with的示例

我希望有一个很好的例子,为每个功能运行,让,应用,也。

我已经阅读了这篇文章,但仍然缺少一个例子。

51885 次浏览

所有这些函数都用于切换当前函数/变量的作用域。它们用于将属于同一位置的内容保存在一起(主要是初始化)。

以下是一些例子:

run-返回所需的任何内容,并将使用该变量的范围重新限定为this

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


generate()
}

密码生成器现在被重新设置为this,因此我们可以设置seedhashhashRepetitions,而无需使用变量。 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将引用苹果,并且将引用水果篮。


注意:我不知羞耻地_举了ABC_0的例子

还有一些文章,如在这里在这里,值得一看。

我认为这是当你需要一个更短,更简洁的几行,并避免分支或条件语句检查(如如果不空,然后这样做)。

我喜欢这个简单的图表,所以我把它链接在这里。您可以从Sebastiano Gottardo编写的中看到它。

enter image description here

也请看下面我解释的图表。

概念

当你调用这些函数时,我认为这是你代码块中的一种角色扮演方式,无论你是否想要自己回来(链调用函数,或设置为结果变量等)。

以上是我的想法。

概念示例

让我们看看这里所有的例子。

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)
}

看待它的方式

我倾向于把alsolet看作是外部的东西。每当你说这两个词的时候,就像你在试图做什么事。let在这台计算机上安装病毒,also使它崩溃。所以这就明确了你是不是演员的问题。

对于结果部分,它显然在那里。also表示它也是另一个东西,所以你仍然保留对象本身的可用性。因此,它将其作为结果返回。

其他所有内容都与this相关联。此外,run/with显然对返回对象自身不感兴趣。现在你可以区分它们了。

我认为,有时当我们从100%的编程/基于逻辑的例子中走出来时,我们就能更好地将事物概念化。但这要视情况而定:)

Let,also,apply,takeif,takeunless是Kotlin中的扩展函数。

要理解这些函数,您必须了解Kotlin中的扩展函数λ函数

扩展功能:

通过使用扩展函数,我们可以在不继承类的情况下为类创建函数。

与C#和Gosu类似

,Kotlin提供了扩展类的功能 使用新功能,而不必从类继承或使用 任何类型的设计模式,如装饰器。这是通过特殊的 称为扩展的声明。Kotlin支持扩展函数 和扩展名属性.

因此,要查找是否只有_ABC中的数字_0,您可以创建一个如下所示的方法,而无需继承String类。

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

您可以像这样使用上面的扩展函数

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

即打印true

LAMBDA函数:

Lambda函数就像Java中的接口。但在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() }

在上面的示例中,let将字符串作为其lambda函数的参数,并返回

以同样的方式,其他扩展函数工作。

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)
}

在上面的示例中,您可以看到String类的函数在lambda函数中直接调用。

武夫

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

除非

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()) }

仅当与regex不匹配时,number才具有phoneNumber的字符串。否则,将null

您可以在此处查看类似的有用答案,Kotlin的区别还有,Kotlin中的apply、let、use、takeif和takeunless

根据我的经验,由于这样的函数是没有性能差异的内联语法糖,您应该始终选择需要在LAMDA中编写最少代码量的函数。

为此,首先确定您希望lambda返回其结果(选择run/let)还是对象本身(选择apply/also)。然后,在大多数情况下,当lambda是单个表达式时,选择与该表达式具有相同块函数类型的表达式,因为当它是接收器表达式时,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"

然而,当Lambda由它们混合组成时,则由您来选择更适合上下文或您觉得更舒服的那个。

此外,当需要解构时,使用具有参数块功能的:

val pair: Pair<TypeA, TypeB> = ...


pair.run/*apply*/ {
val (first, second) = this
...
} // longer
pair.let/*also*/ { (first, second) -> ... } // shorter

下面是Coursera面向Java开发人员的Kotlin上JetBrains的官方Kotlin课程中所有这些功能的简要比较: Difference table Simplified implementations

有6种不同的范围界定功能:

  1. T.快跑
  2. T.让
  3. t.应用
  4. T.还有
  5. 运行

我准备了如下的视觉笔记来显示不同之处:

data class Citizen(var name: String, var age: Int, var residence: String)

enter image description here

决定取决于您的需求。不同功能的用例相互重叠,因此您可以根据项目或团队中使用的特定约定来选择功能。

尽管作用域函数是使代码更简洁的一种方法,但应避免过度使用它们:这会降低代码的可读性并导致错误。避免嵌套作用域函数,并且在链接它们时要小心:很容易混淆当前上下文对象和this或it的值。

下面是另一个图表,用于决定从UNK1@elye.project/掌握-Kotlin-Standard-Functions-Run-With-Let-Also-and-Apply-9cd334b0ef84中使用哪一个 enter image description here

一些约定如下:

使用“_ABC ”_0可执行不会改变对象的其他操作,例如记录或打印调试信息。

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)

如果需要阴影,请使用运行

fun test() {
var mood = "I am sad"


run {
val mood = "I am happy"
println(mood) // I am happy
}
println(mood)  // I am sad
}

如果需要返回接收器对象本身,请使用申请

我必须承认,乍一看,差异并不明显,因为这5个功能通常是可以互换的。以下是我的理解:

适用->;使用这些属性和等待对象初始化对象

val paint = Paint().apply {
this.style = Paint.Style.FILL
this.color = Color.WHITE
}

->;隔离一段代码并等待结果

val result = let {
val b = 3
val c = 2
b + c
}

val a = 1
val result = a.let {
val b = 3
val c = 2
it + b + c
}

val paint: Paint? = Paint()
paint?.let {
// here, paint is always NOT NULL
// paint is "Paint", not "Paint?"
}

还有->;同时执行2个操作,并等待结果

var a = 1
var b = 3
a = b.also { b = a }

->;对此变量/对象执行某些操作并不要等待结果。(不允许链接)

with(canvas) {
this.draw(x)
this.draw(y)
}

快跑->;对此变量/对象执行某些操作,并不要等待结果。(允许链接)

canvas.run {
this.draw(x)
this.draw(y)
}

canvas.run {this.draw(x)}.run {this.draw(x)}