概述
范围函数的目的是为了在一个代码块中持有上下文下对象并执行该代码块。完全由标准库提供,让代码更简洁和易读。此类型的函数共有五个let,run,with,apply,aslo。
以let为例:
Person("Alice", 20, "Amsterdam").let {
println(it)
it.name = "Jack"
it.age = 21
println(it)
}
传统写法:
val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.name = "jack"
alice.age = 21
println(alice)
这几个范围函数它们之间的主要区别主要有两点:
- 引用上下文对象的方式;
- 函数的返回值;
上下文对象(context object)
- 带有接受者的函数字面值:
this:run,with,apply; - 作为
lambda表达式的单个参数的隐式名称:it:let、also。
通过it和this在代码块中访问上下文对象,this可以被省略,it不能被省略。
返回值(return value)
apply和also返回上下文对象,let,run,with返回lambda函数的返回值(使用限定的返回语法从 lambda 显式返回一个值。 否则,将隐式返回最后一个表达式的值)
let
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
代码块中通过it访问上下文对象,返回值是lambda值。
如果代码快中只包含一个it作为参数的单个函数,可以使用方法引用(::)代替lambda。
var numbers = mutableListOf("one", "two", "three", "four", "five")
fun main() {
numbers.map { it.length }.filter { it > 3 }.let { print(it) }
}
let 的常见用法:
- 通过安全操作符
?.,使用let函数处理需要针对一个可null的对象统一做判空处理。
val str: String? = "Hello"
//processNonNullString(str) // compilation error: str can be null
val length = str?.let {
println("let() called on $it")
it.length
}
- 引入局部变量来改善代码的可读性。将
lambda函数的默认参数it变换成firstItem
val numbers = listOf("one", "two", "three", "four")
val modifiedFirstItem = numbers.first().let { firstItem ->
println("The first item of the list is '$firstItem'")
if (firstItem.length >= 5) firstItem else "!" + firstItem + "!"
}.toUpperCase()
println("First item after modifications: '$modifiedFirstItem'")
with
with this object, do the following
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
代码块中通过this访问上下文对象,返回lambda值。
推荐用法:
- 访问上下文对象的方法。而不接受返回值。
val numbers = mutableListOf("one", "two", "three")
with(numbers) {
println("'with' is called with argument $this")
println("It contains $size elements")
}
run
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
代码块中通过this访问上下文对象,返回lambda值。
适用于let,with函数任何场景,避免了with函数无法做空安全检测的问题,可以像let函数使用?.一样做非空判断。也可以解决let函数只能显示通过it来调用上下文对象的方法,而通过隐示的this来访问。
apply
apply the following assignments to the object.
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
代码块中通过this来访问上下文对象,返回对象本身。 apply一般用于一个对象实例初始化的时候,需要对对象中的属性进行赋值。
also
and also do the following
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
代码块中通过it访问上下文对象,返回值为对象本身。
使用方式与let类似,区别是返回值对象本身。
总结
| 函数名 | 上下文对象 | 返回值 | 是否拓展函数 |
|---|---|---|---|
| let | it | lambda result | yes |
| with | this | lambda result | no |
| run | this | lambda result | yes |
| run | - | lambda result | no |
| apply | this | context object | yes |
| also | it | context object | yes |