Kotlin 的内联函数
在 Kotlin 中,inline如果没有使用内联函数,那么我们给函数传 Lambda 时,编译器会悄悄创建一个匿名类实例。而inline—— 相当于 “复制粘贴代码”,消除中间开销。
为啥要使用内联函数?
在 Kotlin 中,函数内联(inline 关键字修饰函数)的核心好处是消除 “函数类型参数 / Lambda 带来的运行时开销”。如果没有使用内联函数,那么我们给函数传 Lambda 时,编译器会悄悄创建一个匿名类实例。带来的问题:
- 额外的对象创建开销:每次调用带 Lambda 参数的函数,都会生成新的临时对象,频繁调用时(如循环中)会增加内存占用和 GC 压力;
- 额外的函数调用开销:Lambda 作为对象,调用时会触发一次间接函数调用(不是直接执行代码),虽开销小,但高频场景下会累积。
而 inline 函数的核心原理是:编译时把函数的 “代码体” 直接嵌入到调用处,而不是创建函数对象、执行函数调用—— 相当于 “复制粘贴代码”,消除中间开销。底层原理是 Define,宏定义。
内联函数的好处:
1. 消除 Lambda 带来的运行时开销。eg:
// 非内联函数:带函数类型参数
fun nonInlineFun(action: ()->Unit) {
action() // 调用 Lambda(间接调用,有开销)
}
// 内联函数:用 inline 修饰
inline fun inlineFun(action: ()->Unit) {
action() // 编译时会直接替换成 Lambda 的代码
}
// 调用场景:循环 1000 次
fun main() {
// 非内联:每次调用都会创建一个 ()->Unit 实例,共 1000 个对象
for (i in 1..1000) {
nonInlineFun { println("非内联:$i") }
}
// 内联:编译时把 { println("内联:$i") } 直接嵌入循环,无对象创建
for (i in 1..1000) {
inlineFun { println("内联:$i") }
}
}
2. 支持非局部返回(不用退出lambda表达式之后再次 return)。eg:
// 非内联函数
fun nonInlineCheck(action: ()->Unit) {
println("非内联:开始")
action() // Lambda 中的 return 只退出这里
println("非内联:结束") // 一定会执行
}
// 内联函数
inline fun inlineCheck(action: ()->Unit) {
println("内联:开始")
action() // Lambda 中的 return 会直接退出 main 函数
println("内联:结束") // 不会执行
}
fun main() {
// 调用非内联函数:Lambda 的 return 是局部返回
nonInlineCheck {
println("非内联 Lambda")
return@nonInlineCheck // 只能退出 Lambda,等价于「局部 return」
}
// 调用内联函数:Lambda 的 return 是非局部返回
inlineCheck {
println("内联 Lambda")
return // 直接退出 main 函数(外层调用函数)
}
println("main 结束") // 不会执行
}
内联函数的 lambda表达式中的 return 也代表 main 函数的 return
3. 通过 inline 内联函数 + reified 修饰泛型参数,编译器会把函数逻辑 “复制粘贴” 到调用处,并替换泛型参数为具体的实际类型,从而绕开泛型擦除,让泛型参数在运行时能被识别。
(如果没有这样用的话:( obj is T ) 会报错)
eg:
inline fun <reified T> Iterable<*>.filterIsInstance(): List<T> {
val result = mutableListOf<T>()
for (element in this) {
if (element is T) { // 直接判断元素是否为 T 类型
result.add(element)
}
}
return result
}
// 调用
fun main() {
val mixedList = listOf(1, "apple", 3.14, "banana", true)
// 筛选所有字符串
val strList = mixedList.filterIsInstance<String>()
// 筛选所有整数
val intList = mixedList.filterIsInstance<Int>()
println(strList) // [apple, banana]
println(intList) // [1]
}
4. 优化代码执行效率(减少了函数调用栈的开销)
普通函数调用会有函数栈帧的创建 / 销毁的开销,而内联函数会把代码直接嵌入调用处,减少一次函数调用,从而优化执行效率。
适用场景:
1. 函数带有函数类型参数(Lambda):这是内联的核心使用场景,因为可以消除创建 lambda 对象的开销。
2. 函数被高频调用(如循环、集合遍历),这时候内联的性能收益会比较大。
3. 函数代码体较短:如果内联函数代码体很长,嵌入到多个调用处会导致字节码膨胀,反而导致性能下降。
反例:
// 错误示范:代码体很长,且无函数类型参数,内联只会导致字节码膨胀
inline fun longNonLambdaFun(a: Int, b: Int): Int {
// 大量复杂逻辑...
Thread.sleep(100)
return a + b
}
所以说虽然 inline 能够优化参数为 lambda 的函数的性能,但是也不能滥用,因为本身也有字节码的开销。如果滥用,性能上的优化甚至抹不平字节码的开销。
坑点:
Kotlin 中用 lambda 写的递归函数不能被内联!!!
原因:
因为内联的本质是 “复制粘贴代码”,而递归是 “自己调用自己”—— 一旦内联,编译器就得把 “递归调用” 也一起复制粘贴,结果可能就是无限循环地复制代码。比如递归调用 1 次就要复制 1 次函数体,调用 100 次就要复制 100 次,但递归的深度是不确定的。
eg:
// 定义 inline 函数,接收一个 lambda(用于递归计算阶乘)
inline fun calculateFactorial(n: Int, action: (Int) -> Long): Long {
return action(n)
}
fun main() {
// 尝试用 lambda 写递归(自己调用自己)
val factorial: (Int) -> Long = { num ->
if (num <= 1) 1L
else num * factorial(num - 1) // lambda 内部调用自身(递归)
}
// 调用 inline 函数,传入递归 lambda
val result = calculateFactorial(5, factorial)
println(result) // 理论上想要 120,但编译器会报警告
}
编译器会发现这个问题,所以会主动拒绝内联,并且抛出提示警告。
更多推荐



所有评论(0)