内联函数 (Inline)
Kotlin 引入 inline 关键字主要是为了优化高阶函数的性能,同时它也解锁了一些普通函数无法实现的功能(如 Reified Type)。
为什么要内联?
在 JVM 上,Lambda 表达式通常会被编译为匿名类对象。这会带来两个开销:
- 内存分配:为 Lambda 创建对象。
- 调用开销:虚方法调用链。
- 变量捕获:如果 Lambda 捕获外部变量,还需要额外的包装对象。
对于像 lock、run、filter 这样频繁调用的小型高阶函数,这些开销是显著的。
运作机制
当函数标记为 inline 时,编译器会将函数体本身以及传递给它的 Lambda 直接复制粘贴到调用处。
inline fun measure(block: () -> Unit) {
val start = System.nanoTime()
block()
println(System.nanoTime() - start)
}
fun main() {
measure { print("Hello") }
}编译后的效果(伪代码):
fun main() {
val start = System.nanoTime()
print("Hello") // Lambda 代码直接展开
println(System.nanoTime() - start)
}完全没有了函数调用和对象创建。
非局部返回 (Non-local Returns)
在普通 Lambda 中,你不能直接使用 return 退出外部函数。但在内联 Lambda 中,由于代码被展开到了外部函数中,return 变得合法且符合直觉。
fun hasZeros(list: List<Int>): Boolean {
list.forEach {
// forEach 是 inline 的,这个 return 直接退出了 hasZeros 函数
if (it == 0) return true
}
return false
}局部控制:noinline 与 crossinline
并非所有参数都需要(或可以)被内联。
noinline
如果你需要将传入的 Lambda 当作对象操作(例如:存储在字段中、传递给其他非内联函数),则必须标记为 noinline。
inline fun register(name: String, noinline task: () -> Unit) {
// 这里的 task 是一个对象,没被内联
TaskManager.add(task)
}crossinline
如果内联函数接收的 Lambda 不是在当前线程直接执行,而是在另一个执行上下文(如嵌套的 Runnable、协程)中间接执行,那么它就**不能 **允许非局部返回(否则会尝试跳出错误的栈帧)。 此时需标记为 crossinline(允许内联优化,但禁止 return)。
inline fun runAsync(crossinline block: () -> Unit) {
Thread {
block() // ✅ 允许,但 block 内部不能写 `return`
}.start()
}具体化类型参数 (Reified) 独门绝技
在 Java/Kotlin 中,泛型在运行时会被擦除(Type Erasure)。你无法检查 T 到底是什么。 但 inline 函数允许我们使用 reified 关键字,让泛型在运行时“活”过来。
原理
因为代码被内联了,编译器在调用处知道确切的类型,它直接把 T 替换成了具体的类(如 String.class)。
// ❌ 普通函数报错: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。
// 之前
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 逻辑内联。
val <T> List<T>.lastIndex: Int
inline get() = size - 1性能权衡:不要滥用
虽然内联好,但也有代价:代码膨胀 (Code Bloat)。 如果一个内联函数体很大,且被四处调用,会导致生成的字节码体积剧增。
最佳实践
- 仅对接收 Lambda 参数的高阶函数使用
inline。 - 如果函数体很大,考虑将核心逻辑提取为私有非内联函数。
- 对于普通函数(不带 Lambda),内联通常没有意义,JIT 编译器会自动优化它们。
总结
- 性能:消除 Lambda 对象开销。
- Reified:突破泛型擦除限制。
- 控制流:允许非局部返回(就像
for循环里的break/return一样自然)。 - 代价:增加字节码体积,需权衡使用。