Kotlin中的内联函数:何时真正需要,何时有害

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

Kotlin中的内联函数:优化魔法还是新手陷阱?



如果你曾经写过Kotlin代码,并查看过标准库,那么你肯定见过函数声明前那个神秘的inline关键字。也许你甚至按照“老手”同事的例子,把它加到了自己的代码中。但你真的理解内联函数何时带来好处,何时只会膨胀字节码并拖慢编译速度吗?



这个问题是Stack Overflow上最热门的问题之一(成千上万的浏览量!),这并非偶然。我们不会复制枯燥的手册。相反,我们将分析三个内联函数成为你最好朋友的真实场景,以及一个最好避免使用它们的场景。开始吧!



30秒理解内联函数(复习一下)



当你用关键字inline标记一个函数时,编译器不会在内存中为它创建一个单独的对象。相反,它会将函数体直接“粘贴”到每个调用它的地方。想象一下,你复制了一段代码并将其直接粘贴到调用点——这就是幕后发生的事情。



不使用inline:

fun measureTime(action: () -> Unit) {    val start = System.currentTimeMillis()    action()    println("Time: ${System.currentTimeMillis() - start}ms")}

fun main() { measureTime { /* 你的代码 */ }}


使用inline:

inline fun measureTime(action: () -> Unit) {    val start = System.currentTimeMillis()    action()    println("Time: ${System.currentTimeMillis() - start}ms")}

fun main() { measureTime { /* 你的代码 */ }}


肉眼看不到区别,但编译器会生成完全不同的字节码。在第一种情况下,会创建一个匿名的lambda对象;在第二种情况下,代码会被直接复制。现在我们来分析一下这何时真正重要。



场景#1:带lambda的高阶函数(最常见的情况)



如果你的函数接受另一个函数作为参数(即所谓的高阶函数),每次调用lambda都会创建一个匿名类。在每秒调用数千次的热点代码中,这可能导致:

  • 堆中不必要的对象分配
  • 垃圾回收器的负担
  • 性能损失高达10-20%


内联何时能拯救局面:

  • 你在循环中频繁调用同一个带lambda的函数
  • 你的函数是库的一部分,你不知道别人会如何使用它
  • lambda捕获了很多变量(内联可以减少开销)


// 不使用inline不好(如果频繁调用)fun repeatAction(times: Int, action: () -> Unit) {    for (i in 0 until times) {        action()    }}

// 使用inline很好inline fun repeatAction(times: Int, action: () -> Unit) { for (i in 0 until times) { action() }}


但有一个细节:如果你的函数很少被调用(比如每分钟一次)或者lambda很大,那么内联带来的好处几乎为零。不要“以防万一”地使用inline。



场景#2:Reified泛型——在运行时处理类型的唯一方法



在Java和Kotlin中,泛型在编译时会被擦除。你不能在普通函数中编写if (value is T)或T::class.java。但是使用inline和reified,这就成为可能!



现实生活中的例子——根据类型创建实例:



// 这不会编译fun <T> createInstance(): T {    return T::class.java.newInstance() // 错误!}

// 而这个——可以工作!inline fun <reified T> createInstance(): T { return T::class.java.newInstance()}


没有inline,你就无法使用reified。这是唯一一种必须使用inline的情况,而不仅仅是

博客

书籍推荐