Skip to content

内联函数 (Inline Functions)

源:Kotlin 内联函数参考指南

使用高阶函数会导致一些运行时惩罚:每个函数都是对象,且会捕获闭包。频繁调用(如在循环中)会带来显著的内存与 CPU 开销。 内联 (Inline) 是 Kotlin 解决此性能问题的杀手锏。

内联原理:代码展开

当你将函数标记为 inline,编译器会将其 函数体 及其 Lambda 参数的代码 直接复制粘贴到调用处。

kotlin
inline fun runOp(block: () -> Unit) {
    println("Start")
    block()
    println("End")
}

fun main() {
    runOp { println("Action") }
}
kotlin
fun main() {
    // 编译器直接将 runOp 的内容搬过来了
    println("Start")
    println("Action") // Lambda 内部的代码也被铺平了
    println("End")
    // 结果:没有任何多余的对象创建和函数调用开销!
}

泛型具体化 (Reified)

由于 JVM 的 类型擦除 (Type Erasure),通常无法在运行时获取泛型 T 的类型。 但因为内联函数会被“展开”,编译器知道实际传入的类型是什么。配合 reified 关键字,我们可以直接访问类型信息。

实战:Android 扩展

不使用 Reified:

kotlin
fun <T : Activity> startActivity(context: Context, clazz: Class<T>) {
    context.startActivity(Intent(context, clazz))
}
// 调用丑陋:
startActivity(context, DetailActivity::class.java)

使用 Reified:

kotlin
inline fun <reified T : Activity> Context.startActivity() {
    // T::class 直接可用!
    startActivity(Intent(this, T::class.java))
}

// 调用优雅:
context.startActivity<DetailActivity>()

非本地返回 (Non-local Returns)

在普通 Lambda 中,你不能直接使用 naked return(必须用 return@label)。但在内联函数的 Lambda 中,由于代码被平铺到外部,你可以直接使用 return 结束 外部函数

kotlin
fun hasZeros(list: List<Int>): Boolean {
    list.forEach { // forEach 是 inline 的
        if (it == 0) return true // ✅ 直接结束 hasZeros 函数
    }
    return false
}

控制内联行为

noinline

如果内联函数有多个 Lambda 参数,你可以选择只内联其中一部分。标记为 noinline 的参数会被保留为普通函数对象(以便存储或传递)。

kotlin
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { ... }

crossinline

如果你需要在内联函数中,将 Lambda 传递给另一个 非内联 上下文(比如 runOnUiThreads { ... }),编译器会报错,防止你通过该 Lambda 意外结束外部函数。 标记 crossinline 承诺:此 Lambda 一定不会执行 return(非本地返回)。

kotlin
inline fun executeAsync(crossinline body: () -> Unit) {
    // 假如没有 crossinline,这里会报错,因为 body 可能包含 return,
    // 而 executeAsync 内联后,return 会试图结束调用者,这在异步线程是不可能的。
    thread { body() } 
}

限制与建议

访问限制

public 的内联函数不能访问 privateinternal 的成员。这是因为内联代码会被复制到使用者的代码中,如果不受限,就会破坏封装性。

什么时候不该用 Inline?

  1. 函数体巨大:内联会导致调用处代码膨胀 (Code Bloat)。
  2. 不接收 Lambda 参数:没有 Lambda 参数的函数内联意义不大(JIT 编译器本身就在做普通内联优化)。