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 教程。