注册

kotlin 作用域函数

在Kotlin标准库(Standard.kt)中定义了几个作用域函数,其中包含letrunwithapplyalso。这几个函数有一个共同点就是在一个对象的上下文中执行代码块。



当对一个对象调用一个函数并提供一个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")

总结


对于各个函数之间的区别可以参考下面的表格。根据使用场景选择合适的函数。


函数对象引用返回值是否时扩展函数
letitLambda 表达式结果
runthisLambda 表达式结果
run-Lambda 表达式结果不是:调用无需上下文对象
withthisLambda 表达式结果不是:把上下文对象当做参数
applythis上下文对象
alsoit上下文对象

以下是根据预期目的选择作用域函数的简短指南:



  • 对一个非空(non-null)对象执行 lambda 表达式:let
  • 将表达式作为变量引入为局部作用域中:let
  • 对象配置:apply
  • 对象配置并且计算结果:run
  • 在需要表达式的地方运行语句:非扩展的 run
  • 附加效果:also
  • 一个对象的一组函数调用:with

参考


Kotlin语言中文站


0 个评论

要回复文章请先登录注册