Kotlin 的 内联函数(inline function) 是一种优化手段,它允许在编译时将函数的代码“展开”到调用处,从而避免函数调用的开销,尤其适用于 高阶函数(接受函数作为参数) 的场景。
什么是内联函数
正常函数调用会产生函数对象、栈帧和跳转指令,但内联函数会在编译时将函数代码插入到调用点,避免这些开销。
inline fun doSomething(block: () -> Unit) {
println("Before")
block()
println("After")
}
fun main() {
doSomething {
println("Hello")
}
}
编译后大致等价于:
fun main() {
println("Before")
println("Hello")
println("After")
}
函数体会“复制粘贴”到调用点。
什么时候使用 inline
Kotlin 中高阶函数(比如 let
、run
、with
、apply
)频繁使用内联,避免函数对象的创建(block: () -> Unit
不会生成 Function 对象),避免 lambda 的闭包捕获、堆分配和 GC 开销。普通业务函数不推荐使用内联,会导致代码膨胀。
inline 的工作原理
内联函数不是运行时特性,而是编译器替换机制。inline fun foo(block: () -> Unit)
编译器看到后就会展开 block()
的实现到调用点。如果有多个调用点,会展开多次(可能造成代码膨胀)。
使用 return@label
可以局部返回:
inline fun doSomething(block: () -> Unit) {
block()
}
fun main() {
doSomething {
println("Inside block")
return@doSomething // 局部返回,不影响 main
}
println("After")
}
如果不加 inline 会怎么样,编译器会生成 Function 实例(Function0),lambda 体被封装为对象,可能造成性能下降,尤其是在 Android 中。
fun doSomething(block: () -> Unit) {
block()
}
noinline 与 crossinline
noinline
默认情况下,所有函数参数都会被内联。但你可以用 noinline
排除部分参数:
inline fun test(block1: () -> Unit, noinline block2: () -> Unit) {
block1() // 被内联
block2() // 作为函数对象
}
noinline
主要用于函数参数需要作为对象传递,以及不能内联的上下文(如存储到变量)。
inline fun doOps(
inlineBlock: () -> Unit,
noinline logging: () -> Unit // 这里阻止了 logging 被内联
) {
inlineBlock() // 内联
val func = logging // 如果不加 noinline 会编译错误,因为不能传递被内联的 lambda
func()
}
crossinline
禁止 lambda 用 return
做“非局部返回”。默认情况下,内联 lambda 可以直接 return
,但有风险。
inline fun higherOrder(block: () -> Unit) {
println("Before")
block()
println("After") // 如果 block 里直接 return,这行永远不会执行
}
fun test() {
higherOrder {
return // 这是“非局部返回”:会从 test() 函数直接返回
}
}
要禁止这种“跳出上层函数”的行为,加 crossinline
:
inline fun higherOrder(crossinline block: () -> Unit) {
println("Before")
block()
println("After") // 如果 block 里直接 return,这行永远不会执行
}
fun test() {
higherOrder {
return // 现在 block() 里面不能写 return 了,这里会报错,但是依然可以写 return@higherOrder
}
}
注意事项与风险
代码膨胀
inline fun heavy(block: () -> Unit) { ... }
repeat(1000) {
heavy { ... } // 编译后生成 1000 份代码
}
函数体复杂,调用频繁时,会导致APK 变大,方法数上升。
调试困难
内联函数展开后,调试栈信息会变得混乱,调试时跳转不准。