Kotlin 内联关键字
最后修改于 2025 年 4 月 19 日
Kotlin 的 inline
关键字用于优化高阶函数。它通过内联函数体来消除函数调用的开销。本教程通过实际例子深入探讨了 inline
关键字。
基本定义
inline
关键字告诉编译器将函数的字节码复制到调用它的地方。这避免了函数对象的运行时开销。它对于将 lambda 表达式作为参数的高阶函数最有利。
基本内联函数
一个简单的内联函数演示了编译器如何用函数体替换调用。这可以提高小型、频繁调用的函数的性能。
package com.zetcode inline fun greet(name: String, action: (String) -> Unit) { println("Hello, $name!") action(name) } fun main() { greet("Kotlin") { name -> println("$name is awesome!") } }
greet
函数被标记为内联。当被调用时,它的主体被直接复制到调用代码中。lambda 参数也被内联,避免了创建函数对象。
性能比较
本例比较了内联函数和非内联函数的性能。区别在小型、频繁调用的函数中最明显。
package com.zetcode inline fun inlineOperation(a: Int, b: Int, op: (Int, Int) -> Int): Int { return op(a, b) } fun normalOperation(a: Int, b: Int, op: (Int, Int) -> Int): Int { return op(a, b) } fun main() { val iterations = 1_000_000 val inlineTime = measureTimeMillis { repeat(iterations) { inlineOperation(5, 3) { x, y -> x + y } } } val normalTime = measureTimeMillis { repeat(iterations) { normalOperation(5, 3) { x, y -> x + y } } } println("Inline time: $inlineTime ms") println("Normal time: $normalTime ms") }
内联版本通常在多次迭代中表现出更好的性能。这是因为它避免了为每个 lambda 调用创建函数对象。这种差异在频繁调用时变得更加显著。
具体化类型参数
内联函数启用具体化类型参数,这些参数在运行时保留类型信息。这允许使用普通泛型无法实现的类型检查和类型转换。
package com.zetcode inline fun <reified T> checkType(obj: Any) { if (obj is T) { println("Object is of type ${T::class.simpleName}") } else { println("Object is NOT of type ${T::class.simpleName}") } } fun main() { checkType<String>("Kotlin") // Output: Object is of type String checkType<Int>("123") // Output: Object is NOT of type Int }
reified
关键字允许在运行时访问实际的类型参数。由于类型擦除,这在使用普通泛型时是不可能的。该函数可以对泛型类型执行运行时类型检查。
内联属性
inline
修饰符也可以应用于属性访问器。这与内联函数类似,优化了属性访问。
package com.zetcode class User(val name: String) { var lastAccess: Long = 0 inline get() { println("Getting last access time") return field } inline set(value) { println("Setting last access time") field = value } } fun main() { val user = User("Alice") user.lastAccess = System.currentTimeMillis() println(user.lastAccess) }
lastAccess
的 getter 和 setter 都被标记为内联。编译器将在调用站点内联这些访问器。这对于带有日志记录或验证的简单属性访问器很有用。
Crossinline 和 Noinline
crossinline
和 noinline
修改内联行为。crossinline
确保 lambda 表达式不能使用非局部返回,而 noinline
阻止了特定参数的内联。
package com.zetcode inline fun execute( crossinline task: () -> Unit, noinline callback: () -> Unit ) { task() runLater(callback) } fun runLater(action: () -> Unit) { // Simulate async execution Thread.sleep(100) action() } fun main() { execute( { println("Task executed") }, { println("Callback executed") } ) }
task
参数使用 crossinline
来防止非局部返回。callback
使用 noinline
,因为它被传递给另一个函数。这些修饰符提供了对内联的控制。
集合中的内联函数
Kotlin 的标准库广泛使用内联函数进行集合操作。本例演示了它们内部的工作方式。
package com.zetcode fun main() { val numbers = listOf(1, 2, 3, 4, 5) val squares = numbers.map { it * it } println(squares) // [1, 4, 9, 16, 25] val evens = numbers.filter { it % 2 == 0 } println(evens) // [2, 4] val sum = numbers.fold(0) { acc, num -> acc + num } println(sum) // 15 }
Kotlin 标准库中的 map
、filter
和 fold
等函数是内联的。这使得集合操作即使使用 lambda 表达式也有效。编译器内联函数和 lambda 表达式的主体。
何时不使用内联
此示例显示了内联函数可能没有益处甚至会损害性能的情况。
package com.zetcode // Not good for large functions inline fun largeInlineFunction() { // Hundreds of lines of code... println("This function is too large to benefit from inlining") } // Recursive functions can't be inlined inline fun recursiveFunction(n: Int) { if (n > 0) { println(n) recursiveFunction(n - 1) // Error: recursive inline function } } fun main() { largeInlineFunction() }
当函数体很大时,内联函数会增加代码大小。递归函数不能被内联。此外,公共 API 函数在内联之前可能需要仔细考虑,因为存在二进制兼容性问题。
内联函数的最佳实践
- 用于小型函数: 适用于行数少且经常调用的函数。
- 高阶函数: 最适合接受 lambda 表达式的函数,以避免函数对象开销。
- 具体化类型: 当您需要泛型参数的运行时类型信息时,这至关重要。
- 避免大型函数: 内联大型函数会增加字节码大小,而没有显着的收益。
- 考虑 noinline: 当一些 lambda 表达式需要存储或传递给非内联函数时,使用此选项。
来源
本教程深入介绍了 Kotlin 的 inline
关键字,展示了它在性能和具体化类型方面的优势。我们探讨了各种场景,包括集合、属性和参数修饰符。正确使用内联函数可以使您的 Kotlin 代码更高效,同时保持清晰度。
作者
列出 所有 Kotlin 教程。