作用域函数 let、also、with、run、apply

159 阅读3分钟

在 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/()->RR
T.run/T.()->RR
withTT.()->RR
T.apply/T.()->UnitT
T.also/(T)->UnitT
T.let/(T)->RR

还是一脸懵逼,那我提几个问题:

  • 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 是函数参数;

  • 为什么withthisletit

    • 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)。