Kotlin标准库中的apple/also/run/let这四个函数相当有意思。它们的实现非常简单,区区两三行,却直击了Java的若干痛点。
Kotlin对它们的定位是scope functions。这是什么意思呢?我理解,scope functions是指这些函数可以通过新的作用域操作对象,避免引入临时变量或一次性函数污染原作用域。
首先,让我们来看看apply:
public inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
寥寥数行,信息量却很大。
此函数有如下几个特点:
- 使用
inline:跟C的inline类似,Kotlin的inline也是在编译期将函数调用直接在调用处展开,以节省函数调用的开销;所不同的是,Kotlin还会把传入的lambda函数也展开。 - 结合泛型和扩展,即
T.apply,使得它成为了任意对象的成员函数 - 唯一的参数
block是一个lambda函数,且block的参数是T.(),相当于为T扩展了一个无参的成员函数 - 由于
apply和block都相当于是T的成员函数,在这两个函数的作用域里的this都指的是T的一个实例 - 执行传入的
block,并返回this
apply如何使用呢?一个例子:
class House {
val window = Window().apply {
location = "Living Room"
color = Color.WHITE
size = Size.LARGE
}
}
这个例子写成Java将会是这样:
class House {
private final Window window = createWindow();
private Window createWindow() {
Window window = new Window();
setLocation("Living Room");
setColor(Color.WHITE);
setSize(Size.LARGE);
return window;
}
}
可以看出,利用apply我们避免了引入createWindow这种一次性使用的函数污染House的成员。
(当然,对于上面的例子,我们可以通过给Window加一个Builder来避免引入额外的函数createWindow。但如果我们还需要调用setter以外的方法呢? )
下面,我们来看看also:
public inline fun <T> T.also(block: (T) -> Unit): T {
block(this)
return this
}
诶?also和apply不是一样的吗?非也。这两个函数对block的定义不同。之前的apply中,block的定义是T.() -> Unit,可作为T的扩展函数;而also的是(T) -> Unit,它没有扩展T,而是把T作为一个参数。
also适用于需要把T传递给其它对象的函数的场景。例如:
fun newHouseWithAWindow() {
return House()
.add(Window())
.also { log.debug("Created a new house with a window. house=$it") }
}
而用Java写的话,我们为了能够log返回值,需要引入一个局部变量:
Window newHouseWithAWindow() {
House house = new House().add(new Window());
log.debug("Created a new house with a window. {}", house);
return house;
}
最后是run和let:
public inline fun <T, R> T.run(block: T.() -> R): R = block()
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
run与apply类似,let与also类似。所不同的是,这两个函数在执行完block之后,返回的是block的返回值,而非this。
总而言之,这四个函数的异同点在于:
- 传入的
block的作用域里的是this还是it - 返回值是
this还是block的执行结果
| 函数 | block里 | 返回值 |
|---|---|---|
| apply | this | this |
| also | it | this |
| run | this | block的结果 |
| let | it | block的结果 |
那么,怎么选择应该用哪个函数呢?Kotlin官方文档Coding Conventions中有一节Using scope functions apply/with/run/also/let对此已有解答,这里不再赘述。