Kotlin 作用域函数 let 的实现原理
Kotlin 中的 let 是一个
标准库扩展函数,它广泛用于作用域函数(Scope
Functions)中,尤其适用于对可空对象(nullable)做非空判断并执行代码块的场景。
示例代码
1 | val name: String? = "123" |
这个例子等价于:
1 | if (name != null) { |
也就是说,name?.let { ... } 只有当 name
非空时才执行 let 的 lambda 块。lambda 表达式中
it 就是 name 的非空值。let 返回
lambda 的返回值。
实现原理
Kotlin 的 let 函数定义在
commonMain/kotlin/util/Standard.kt 中,源码如下:
1 | .internal.InlineOnly |
它是一个
内联(inline)函数,在编译时会被内联展开,避免 lambda
带来的性能开销。泛型 <T, R> 表示接收一个类型为
T 的对象,返回一个类型为 R
的结果。T.let 表示 let 是类型 T
的扩展函数。block: (T) -> R 是接收 T
的函数(lambda 表达式)。也就是说,它只是将当前对象 this
传入了 block(this) 中。
@kotlin.internal.InlineOnly,这是一个
注解(Annotation),用于标记某个函数 只能在被
inline(内联)时使用,否则编译器会报错。
比如下面的代码:
1 |
|
表示这个 let 函数
不会生成实际函数调用(它只能内联展开),避免 Java 或非
Kotlin 编译器调用这个方法。
为什么要限制只能 inline?为了提高性能,避免生成函数对象和调用开销,同时 确保代码安全地被内联使用,防止其他模块通过反射或 Java 调用这个方法。
1 | contract { |
这是Kotlin 的 Contract DSL,用于
给编译器更多关于 lambda
执行行为的信息,提升智能分析、空安全和优化。表示block
这个 lambda 参数,在函数调用过程中会
被调用且只调用一次(Exactly
once)。这使得编译器可以进行一些静态分析优化,例如在下面这种空检查中,判断
name 非空:
1 | val name: String? = "abc" |
InvocationKind 类型说明:
EXACTLY_ONCE:block 会被调用且仅一次AT_LEAST_ONCE:一定会调用一次或多次AT_MOST_ONCE:最多一次,可能不调用UNKNOWN:不确定
编译器依靠这个信息进行控制流分析,提升非空智能推断、性能优化、检测死代码等能力。
R 是泛型返回类型。
1 | inline fun <T, R> T.let(block: (T) -> R): R { |
<T, R> 是泛型声明,T是调用
let
的对象类型(接收者),R是block
函数返回值的类型,也是 let 函数的最终返回值类型。
举例:
1 | val name = "abc" |
也可以是任意类型:
1 | val upper = "abc".let { it.uppercase() } // R = String |
编译后字节码
比如:
1 | val name: String? = "123" |
大致翻译成 Java 是:
1 | String name = "123"; |
编译器把 ?.let { ... } 直接转成了
if != null 的判断。lambda
是内联展开的,不会有额外函数对象生成,所以效率非常高。
常见用途
处理 nullable 类型:
1 | val name: String? = getName() |
链式调用:
1 | val result = listOf(1, 2, 3).map { it * 2 }.let { |
限定作用域变量(避免变量污染):
1 | val userInput = readLine() |