在 Kotlin 标准库中,定义了一系列通用的内联函数:T.apply、T.also、T.let、T.run、with。你是否清楚理解它们的用法 & 本质,它们都是扩展函数吗?
val str1: String = "".run {
println(this.length)
this
}
val str2: String = with("") {
println(this.length)
this
}
val str3: String = "".apply {
println(this.length)
}
val str4: String = "".also {
println(it.length)
}
val str5: String = "".let {
println(it.length)
it
}
在上面的示例中,我们看到有的函数作用域内使用了this,而其它又使用了it。这两个关键字到底引用的是什么,为什么会有差别呢?
我们先找到这些函数的声明:
standard.kt
public inline fun <R> run(block: () -> R): R {
return block()
}
public inline fun <T, R> T.run(block: T.() -> R): R {
return block()
}
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
public inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
public inline fun <T> T.also(block: (T) -> Unit): T {
block(this)
return this
}
public inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
一脸懵逼,别急,我们梳理一下:
| 函数 | 参数1 | 参数2 | 返回值 |
|---|---|---|---|
run | / | ()->R | R |
T.run | / | T.()->R | R |
with | T | T.()->R | R |
T.apply | / | T.()->Unit | T |
T.also | / | (T)->Unit | T |
T.let | / | (T)->R | R |
还是一脸懵逼,那我提几个问题:
-
runvsT.run,差了一个T,区别是什么?区别在于:
run是普通函数,T.run是扩展函数。run中的this是声明的类对象(顶级函数除外),T.run中的this是接收者对象; -
T.()->Unitvs(T)->Unit,或者T.()->Rvs(T)->R,T 的位置不同,区别是什么?区别在于:
T.()->Unit中的 T 是接收者类型,(T)->Unit中的 T 是函数参数; -
为什么
with用this,let用it?- run、with、apply 函数中的参数 block 是 「T 的扩展函数」,所以采用 this 是扩展函数的接收者对象(receiver)。另外因为 block 没有参数,所以不存在 it 的定义。
- also 和 let 参数 block 是 「参数为 T 的函数」,所以采用 it 是唯一参数(argument)。另外因为 block 不是扩展函数,所以不存在 this 的定义。
lambda 表达式
lambda 表达式本质上是 「可以作为值传递的代码块」。在老版本 Java 中,传递代码块需要使用匿名内部类实现,而使用 lambda 表达式甚至连函数声明都不需要,可以直接传递代码块作为函数值。
当 lambda 表达式只有一个参数,可以用
it关键字来引用唯一的实参。
总结
- 扩展可以在不修改类 / 不继承类的情况下,向一个类添加新函数或者新属性,更符合开闭原则。相对于传统 Java 的工具方法的调用方式更简单直接,表意性更强;
- 扩展函数是定义在类外部的静态函数,函数的第一个参数是接收者类型,调用扩展时不会创建适配对象或者任何运行时的额外消耗。在 Java 中,我们只需要像调用普通静态方法那样调用扩展即可;
- 标准库提供的函数中,run、with、apply 函数中的参数 block 是「T 的扩展函数」,所以采用 this 是扩展函数的接收者对象(receiver);also 和 let 参数 block 是「参数为 T 的函数」,所以采用 it 是唯一参数(argument)。