Kotlin 标准库中有五个实用的函数:let
、also
、run
、apply
和 with
,它们很好地利用了 Kotlin 函数式编程的写法,可以让我们的代码更加简洁美观。
如果你还不会 Kotlin 的 lambda 表达式和函数式接口,可以先参考我的另一篇文章:Kotlin 学习:函数式编程(Lambda 表达式和函数式接口)。
let
用 obj.let{}
可以简化对变量 obj
的操作,常用于对可空变量统一做判空处理。
什么叫“统一做判空处理”呢?假如一个 Java 程序员,刚学一点 Kotlin,按以前写 Java 的思路,可能会写出这样的代码:
val list = getList()
if (list != null) {
list.add("apple")
list.remove("orange")
}
val fruits = list.toString()
println(fruits)
这样的代码完全没有用上 Kotlin 的特性,仍然是 Java 风格。如果使用 Kotlin 的空值判断,再用上扩展函数 let
,就可以改写成这样:
val list: MutableList<String>? = getList()
val fruits = list?.let {
it.add("apple")
it.remove("orange")
it.toString()
}
println(fruits)
它是怎么实现的呢?我们来解析一下 let
函数的源码:
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
let
是一个扩展函数,它的参数只有一个 lambda 表达式。函数体中,它把调用 let
函数的对象作为参数传给了这个 lambda 表达式,并把 lambda 表达式的执行结果作为 let
函数的返回值。
在上面的例子当中,你可以理解为 let
函数将 lambda 表达式的最后一行最为它的返回值,并且我们省略了 lambda 表达式的参数,用 it
去指代它,即调用 let
函数的对象 list
。如果 list
为空,那么 let
中的 lambda 表达式都不会执行,而是返回一个 null
。
在实际应用中,我们可能会遇到 let
函数的多层嵌套,都用 it
可能会产生混淆,这时就可以显式指定 lambda 表达式的参数,给它起一个别名:
val fruits = list?.let { newFruits ->
newFruits.add("apple")
newFruits.remove("orange")
val ateFruits = list2?.let { ateFruits ->
ateFruits.add("orange")
}
newFruits.toString()
}
also
与 let
函数类似,先来看看它的源码:
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
区别就是 also
函数的返回值是调用 also
函数的对象本身,而不是 lambda 表达式的最后一行。
val list: MutableList<String>? = getList()
list?.also {
it.add("apple")
it.remove("orange")
}
val fruits = list.toString()
println(fruits)
run
老样子,先看源码:
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
与 let
函数类似,也可用作统一判空处理,但 run
函数的参数从 block: (T) -> R
变成了 block: T.() -> R
,block
成了 T
的扩展函数,这样 lambda 表达式就会自动拥有 T
的上下文。
也就是说当我们在 run
函数内调用对象的属性和方法时,可以省略掉 it
(或者说省略掉 this
):
val list: MutableList<String>? = getList()
val fruits = list?.run {
add("apple")
remove("orange")
toString()
}
println(fruits)
apply
源码:
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
与 run
函数类似,区别就是 apply
函数不会把函数体最后一行作为返回值,而是返回调用对象本身,就像 also
与 let
的区别一样:
val list: MutableList<String>? = getList()
list?.apply {
add("apple")
remove("orange")
}
val fruits = list.toString()
println(fruits)
with
源码:
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
与 run
函数的作用类似,但它与前四个函数有一个最大的区别:with
不是扩展函数,而是有两个参数,第一个参数 receiver
为要操作的对象,第二个参数为 lambda 表达式,所以写法从 obj.run{}
改成了 with(obj) {}
:
val list: MutableList<String>? = getList()
val fruits = with(list) {
add("apple")
remove("orange")
toString()
}
println(fruits)
with
函数就不便于做判空处理了。