为啥要使用内联函数?

在 Kotlin 中,函数内联(inline 关键字修饰函数)的核心好处是消除 “函数类型参数 / Lambda 带来的运行时开销”。如果没有使用内联函数,那么我们给函数传 Lambda 时,编译器会悄悄创建一个匿名类实例。带来的问题:

  1. 额外的对象创建开销每次调用带 Lambda 参数的函数,都会生成新的临时对象,频繁调用时(如循环中)会增加内存占用和 GC 压力;
  2. 额外的函数调用开销: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,但编译器会报警告
}

编译器会发现这个问题,所以会主动拒绝内联,并且抛出提示警告。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐