Skip to content

内联函数 (Inline)

源:Inline functions

Kotlin 引入 inline 关键字主要是为了优化高阶函数的性能,同时它也解锁了一些普通函数无法实现的功能(如 Reified Type)。

为什么要内联?

在 JVM 上,Lambda 表达式通常会被编译为匿名类对象。这会带来两个开销:

  1. 内存分配:为 Lambda 创建对象。
  2. 调用开销:虚方法调用链。
  3. 变量捕获:如果 Lambda 捕获外部变量,还需要额外的包装对象。

对于像 lockrunfilter 这样频繁调用的小型高阶函数,这些开销是显著的。

运作机制

当函数标记为 inline 时,编译器会将函数体本身以及传递给它的 Lambda 直接复制粘贴到调用处。

kotlin
inline fun measure(block: () -> Unit) {
    val start = System.nanoTime()
    block()
    println(System.nanoTime() - start)
}

fun main() {
    measure { print("Hello") }
}

编译后的效果(伪代码):

java
fun main() {
    val start = System.nanoTime()
    print("Hello") // Lambda 代码直接展开
    println(System.nanoTime() - start)
}

完全没有了函数调用和对象创建。

非局部返回 (Non-local Returns)

在普通 Lambda 中,你不能直接使用 return 退出外部函数。但在内联 Lambda 中,由于代码被展开到了外部函数中,return 变得合法且符合直觉。

kotlin
fun hasZeros(list: List<Int>): Boolean {
    list.forEach { 
        // forEach 是 inline 的,这个 return 直接退出了 hasZeros 函数
        if (it == 0) return true 
    }
    return false
}

局部控制:noinline 与 crossinline

并非所有参数都需要(或可以)被内联。

noinline

如果你需要将传入的 Lambda 当作对象操作(例如:存储在字段中、传递给其他非内联函数),则必须标记为 noinline

kotlin
inline fun register(name: String, noinline task: () -> Unit) {
    // 这里的 task 是一个对象,没被内联
    TaskManager.add(task) 
}

crossinline

如果内联函数接收的 Lambda 不是在当前线程直接执行,而是在另一个执行上下文(如嵌套的 Runnable、协程)中间接执行,那么它就**不能 **允许非局部返回(否则会尝试跳出错误的栈帧)。 此时需标记为 crossinline(允许内联优化,但禁止 return)。

kotlin
inline fun runAsync(crossinline block: () -> Unit) {
    Thread {
        block() // ✅ 允许,但 block 内部不能写 `return`
    }.start()
}

具体化类型参数 (Reified) 独门绝技

在 Java/Kotlin 中,泛型在运行时会被擦除(Type Erasure)。你无法检查 T 到底是什么。 但 inline 函数允许我们使用 reified 关键字,让泛型在运行时“活”过来。

原理

因为代码被内联了,编译器在调用处知道确切的类型,它直接把 T 替换成了具体的类(如 String.class)。

kotlin
// ❌ 普通函数报错:Cannot check for instance of erased type: T
// fun <T> isType(value: Any) = value is T 

// ✅ 内联 + Reified
inline fun <reified T> isType(value: Any): Boolean {
    return value is T
}

isType<String>("Hello") // true

实战应用

reified 使得很多 API 极其优雅,例如 Gson/Jackson 的序列化,或 Android 的 startActivity

kotlin
// 之前
startActivity(Intent(this, DetailActivity::class.java))

// 之后 (KTX)
inline fun <reified T : Activity> Context.startActivity() {
    startActivity(Intent(this, T::class.java))
}
startActivity<DetailActivity>()

内联属性

inline 也可以修饰没有幕后字段的属性(通常是扩展属性),将其 getter/setter 逻辑内联。

kotlin
val <T> List<T>.lastIndex: Int
    inline get() = size - 1

性能权衡:不要滥用

虽然内联好,但也有代价:代码膨胀 (Code Bloat)。 如果一个内联函数体很大,且被四处调用,会导致生成的字节码体积剧增。

最佳实践

  • 仅对接收 Lambda 参数的高阶函数使用 inline
  • 如果函数体很大,考虑将核心逻辑提取为私有非内联函数。
  • 对于普通函数(不带 Lambda),内联通常没有意义,JIT 编译器会自动优化它们。

总结

  • 性能:消除 Lambda 对象开销。
  • Reified:突破泛型擦除限制。
  • 控制流:允许非局部返回(就像 for 循环里的 break/return 一样自然)。
  • 代价:增加字节码体积,需权衡使用。