Kotlin 学习:标准函数 let、also、run、apply 和 with

Kotlin 标准库中有五个实用的函数:letalsorunapplywith,它们很好地利用了 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.() -> Rblock 成了 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 函数不会把函数体最后一行作为返回值,而是返回调用对象本身,就像 alsolet 的区别一样:

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 函数就不便于做判空处理了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值