kotlin 作用域函数
在Kotlin标准库(Standard.kt)中定义了几个作用域函数,其中包含let
、run
、with
、apply
和also
。这几个函数有一个共同点就是在一个对象的上下文中执行代码块。
当对一个对象调用一个函数并提供一个lambda表达式时,它会形成一个临时作用域。在此作用域中,可以访问该对象而无需其名称。这样的函数称之为
作用域函数
。
这些函数使用起来比较相似,主要区别在于两个方面:
- 应用上下文对象的方式
- 返回值
let
public inline fun <T, R> T.let(block: (T) -> R): R
let
声明为扩展函数,上下文对象作为lambda表达式的参数(默认为it,也可以自定义名称),返回值是lambda表达式的结果。
val result = "a".let {
123
// return@let 123
}
print(result) // 123
上面的代码会输出123
,在lambda表达式中可以省略return
语句,默认最后一行代码为返回值。
let
函数经常用于对象非空执行代码块的情况。例如下面情况使用let
是非常方便的。
val str: String? = "Hello"
//processNonNullString(str) // 编译错误:str 可能为空
val length = str?.let {
println("let() called on $it")
processNonNullString(it) // 编译通过:'it' 在 '?.let { }' 中必不为空
it.length
}
当运行时str
不为空才会执行let
后面的代码块,相比Java
中需要对str
进行非空判断就非常便捷了。
run
public inline fun <R> run(block: () -> R): R
public inline fun <T, R> T.run(block: T.() -> R): R
再标准库中定义了两个run
函数,其中第一个run
函数可以独立运行一个代码块,并将lambda表达式的返回值作为run
函数的返回值。例如:
val hexNumberRegex = run {
val digits = "0-9"
val hexDigits = "A-Fa-f"
val sign = "+-"
Regex("[$sign]?[$digits$hexDigits]+")
}
for (match in hexNumberRegex.findAll("+1234 -FFFF not-a-number")) {
println(match.value)
}
第二个run
函数是一个扩展函数,上下文对象作为接收者(this) 来访问,返回值是lambda表达式结果。
val service = MultiportService("https://example.kotlinlang.org", 80)
val result = service.run {
port = 8080
query(prepareRequest() + " to port $port")
}
// 同样的代码如果用 let() 函数来写:
val letResult = service.let {
it.port = 8080
it.query(it.prepareRequest() + " to port ${it.port}")
}
可以看出这个run
函数与let
类似,区别在于run
中可以直接使用上下文对象的属性和方法,而let
需要通过it
来调用上下文对象的属性和方法。
with
public inline fun <T, R> with(receiver: T, block: T.() -> R): R
with
函数是一个非扩展函数,将上下文对象作为参数传递,并接收一个lambda表达式,在lambda表达式内部可以直接引用上下文对象的属性和方法,并将lambda表达式结果作为with
函数的返回值。
val numbers = mutableListOf("one", "two", "three")
with(numbers) {
println("'with' is called with argument $this")
println("It contains $size elements")
}
with
函数可以理解为“对于这个对象执行以下操作”。在使用with
函数时建议使用 with
来调用上下文对象上的函数,而不使用 lambda 表达式结果。
apply
public inline fun <T> T.apply(block: T.() -> Unit): T
apply
函数是一个扩展函数,上下文对象 作为接收者(this
)来访问。 返回值 是上下文对象本身。
apply
的常见情况是对象配置。这样的调用可以理解为“将以下赋值操作应用于对象”。
val adam = Person("Adam").apply {
age = 32
city = "London"
}
println(adam)
also
public inline fun <T> T.also(block: (T) -> Unit): T
also
函数是一个扩展函数,上下文对象作为 lambda 表达式的参数(it
)来访问。 返回值是上下文对象本身。
also
对于执行一些将上下文对象作为参数的操作很有用。 对于需要引用对象而不是其属性与函数的操作,或者不想屏蔽来自外部作用域的 this
引用时,请使用 also
。
当你在代码中看到 also
时,可以将其理解为“并且用该对象执行以下操作”。
val numbers = mutableListOf("one", "two", "three")
numbers
.also { println("The list elements before adding new one: $it") }
.add("four")
总结
对于各个函数之间的区别可以参考下面的表格。根据使用场景选择合适的函数。
函数 | 对象引用 | 返回值 | 是否时扩展函数 |
---|---|---|---|
let | it | Lambda 表达式结果 | 是 |
run | this | Lambda 表达式结果 | 是 |
run | - | Lambda 表达式结果 | 不是:调用无需上下文对象 |
with | this | Lambda 表达式结果 | 不是:把上下文对象当做参数 |
apply | this | 上下文对象 | 是 |
also | it | 上下文对象 | 是 |
以下是根据预期目的选择作用域函数的简短指南:
- 对一个非空(non-null)对象执行 lambda 表达式:
let
- 将表达式作为变量引入为局部作用域中:
let
- 对象配置:
apply
- 对象配置并且计算结果:
run
- 在需要表达式的地方运行语句:非扩展的
run
- 附加效果:
also
- 一个对象的一组函数调用:
with
参考