在 Kotlin 中,经常会发现使用 let 、apply 关键字后面带有代码块的写法。这些用法是 Kotlin 标准库提供的作用域函数。它们的典型特点是:
通过一个对象调用这些函数,这个对象称为**上下文对象 **。这些函数是 lambda 表达式的形式使用。在 lambda 表达式中,可以访问上下文对象
上下文对象.函数名 { 上下文对象引用 ->
...
}
当然也有比较特殊的 with 和 run 等。实际上有 6 个作用域函数:let
、run
、 非拓展函数的 run
、with
、apply
、also
。
这些函数的作用基本上是一样的:在一个对象上执行一段代码。不同的是这个对象如何在代码块中使用,以及代码块最终的返回结果。
例如,下面这个典型的用法:
Person("Alice", 20, "Amsterdam").let { it ->
println(it)
it.moveTo("London")
it.incrementAge()
println(it)
}
如果不使用 let 函数,你需要声明一个新的变量,并且通过这个变量名重复调用这个对象:
val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.moveTo("London")
alice.incrementAge()
println(alice)
作用域函数没有引入任何新的技术能力,但它们可以让代码更加简洁易读。
由于作用域函数的性质相似,在实际情况中选择正确的函数可能有点棘手。下面我们将详细描述作用域函数之间的区别及其使用约定。
不同之处
为了帮助您为您的目的选择正确的作用域函数,我们提供了它们之间的主要区别表。
函数 | 对象引用 | 返回值 | 是否是拓展函数 |
---|---|---|---|
let | it | Lambda result | YES |
run | this | Lambda result | YES |
run | – | Lambda result | NO,在没有上下文对象的情况下调用 |
with | this | Lambda result | NO,需要一个上下文对象作为参数 |
apply | this | Context 对象本身 | YES |
also | it | Context 对象本身 | YES |
为了方便区分,可以从以下角度进行记忆:
-
run 函数有两个。分别是拓展函数的情况,此时需要通过一个上下文对象调用;另一种情况是直接使用 run 方法,此时不需要依靠任何对象来调用。 -
非拓展函数的作用域函数有两个,run 和 with ,其他都是拓展函数。它们都比较特殊,不需要依赖上下文对象进行调用。run 是无需任何对象就可以调用;with 需要将一个对象作为参数调用。 -
apply 和 also 的返回值是上下文对象本身,其他的都是 lambda 表达式的返回结果。 -
只有 let 和 also 函数的 lambda 表达式中,通过 it 对上下文对象进行使用。 -
非拓展函数 run 不需要依赖一个对象进行调用,所以它没有对象引用。
总体来说,除了拓展函数的区别,作用域函数最重要的区别就是 lambda 中引用上下文对象的方式不同和返回值不同。
上下文对象的引用方式
在作用域函数的 lambda 内部,上下文对象可通过一个代称来进行引用而不是实际变量名本身。每个作用域函数使用两种方式之一来访问上下文对象:
-
作为 lambda 接收器 (this) -
作为 lambda 参数 (it)。
两者都提供相同的功能,因此我们将针对不同情况描述每种功能的优缺点,并就它们的使用提供建议。
fun main() {
val str = "Hello"
// this
str.run {
println("The string's length: $length")
}
// it
str.let {
println("The string's length is ${it.length}")
}
}
this
run
、with
和 apply
将上下文对象称为 lambda 接收器,在它们的 lambda 中使用通过 this 关键字访问上下文对象。在类中使用自身,也是可以通过 this 关键字访问自身实例的。因此,该对象就像在普通类的函数中一样使用。
this 的好处是,可以直接省略 this 本身,直接访问对象,代码更短。但这样也会让你不好区分访问的是类本身中的内容,还是上下文对象中的内容。
因此,对于主要对上下文对象的属性或方法进行操作的 lambda,建议将上下文对象作为接收器 (this):主要是调用上下文对象自身的函数或为其分配属性。
val adam = Person("Adam").apply {
age = 20 // same as this.age = 20
city = "London"
}
println(adam)
it
反过来, let 也将上下文对象作为 lambda 参数。如果未指定参数名称,则通过隐式默认名称访问对象。it 比 this 更短,并且带有 it 的表达式通常比 this 表达式更容易理解。但是 it 不支持 this 一样的隐式调用,不可以省略。因此,当对象主要用作函数调用中的参数时,将上下文对象作为 it 是更合适的。
总结
-
it 更适用于将上下文对象作为参数,或代码块存在多个变量时使用。 -
this 更适用于对上下文对象本身的属性和方法进行操作时使用。
返回值
作用域函数因返回的结果而异,可以分为两组:
-
apply 和 also 返回上下文对象本身。 -
let 、run 和 with 返回 lambda 表达式的结果。
这两个选项可让您根据代码中的下一步操作来选择正确的函数。
上下文对象
apply 和 also 的返回值是上下文对象本身。因此,它们可以使用一个调用链来做一些附加操作。调用一些链式调用方法在同一个对象上附加额外功能。
val numberList = mutableListOf<Double>()
numberList.also { println("Populating the list") }
.apply {
add(2.71)
add(3.14)
add(1.0)
}
.also { println("Sorting the list") }
.sort()
它们也可以用于返回上下文对象的函数的返回语句中。
fun getRandomInt(): Int {
return Random.nextInt(100).also {
writeToLog("getRandomInt() generated value $it")
}
}
val i = getRandomInt()
Lambda 结果
Let 、run 和 with 返回 lambda 表达式结果。因此你可以在需要将结果分配给变量时使用这些方法。
val numbers = mutableListOf("one", "two", "three")
val countEndsWithE = numbers.run {
add("four")
add("five")
count { it.endsWith("e") }
}
println("There are $countEndsWithE elements that end with e.")
此外,您可以忽略返回值并使用范围函数为变量创建临时范围。
val numbers = mutableListOf("one", "two", "three")
with(numbers) {
val firstItem = first()
val lastItem = last()
println("First item: $firstItem, last item: $lastItem")
}
选择困难
这些函数看起来功能并没有太多差别,如何在实际的代码逻辑中选择,官方给我们提供了一些规则:
-
在非空对象上执行 lambda : let
,经常配合可空安全运算符?
使用。 -
在局部范围内将表达式作为变量使用: let
-
对象配置: apply
-
对象配置并计算一个结果: run
-
在需要一个表达式的地方运行语句:非拓展函数的 run
-
附加一些效果: also
-
对对象进行分组函数调用: with
尽管作用域函数是一种使代码更简洁的方法,但请避免过度使用它们:它会降低代码的可读性并导致错误。
避免嵌套作用域函数并在链接它们时要小心:很容易对当前上下文对象和 this 或 it 的值感到困惑。
let
上下文对象可用作参数 (it)。返回值是 lambda 结果。let 通常用于执行仅具有非空值的代码块。要对可空对象执行操作,需要配合 ?
进行使用。使用 let 的另一种情况是引入具有有限范围的局部变量以提高代码可读性。可以将参数 it 命名为其他名称,更容易理解。
with
with 不是拓展函数,上下文对象需要作为参数传递给 with 。with 方法推荐在不需要 lambda 返回结果时使用,意义是 “使用此对象,执行一些操作” 。
拓展函数 run
拓展函数的 run 做的事情和 with 相同。
非拓展函数的 run
非拓展函数的 run 不依赖上下文对象,只是执行一个 lambda 代码块。
apply
上下文对象可用作接收器 (this)。返回值是对象本身。 apply 的常见情况是对象配置。此类调用可以理解为“将以下分配应用于对象”。
also
上下文对象可用作参数 (it)。返回值是对象本身。适用于执行一些将上下文对象作为参数的操作。用于需要引用对象而不是其属性和函数的操作,或者当您不想从外部范围隐藏 this 引用时。
原文始发于微信公众号(八千里路山与海):Kotlin Scope Function
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/85044.html