ZetCode

Kotlin 内联关键字

最后修改于 2025 年 4 月 19 日

Kotlin 的 inline 关键字用于优化高阶函数。它通过内联函数体来消除函数调用的开销。本教程通过实际例子深入探讨了 inline 关键字。

基本定义

inline 关键字告诉编译器将函数的字节码复制到调用它的地方。这避免了函数对象的运行时开销。它对于将 lambda 表达式作为参数的高阶函数最有利。

基本内联函数

一个简单的内联函数演示了编译器如何用函数体替换调用。这可以提高小型、频繁调用的函数的性能。

BasicInline.kt
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 参数也被内联,避免了创建函数对象。

性能比较

本例比较了内联函数和非内联函数的性能。区别在小型、频繁调用的函数中最明显。

PerformanceTest.kt
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 调用创建函数对象。这种差异在频繁调用时变得更加显著。

具体化类型参数

内联函数启用具体化类型参数,这些参数在运行时保留类型信息。这允许使用普通泛型无法实现的类型检查和类型转换。

ReifiedExample.kt
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 修饰符也可以应用于属性访问器。这与内联函数类似,优化了属性访问。

InlineProperty.kt
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

crossinlinenoinline 修改内联行为。crossinline 确保 lambda 表达式不能使用非局部返回,而 noinline 阻止了特定参数的内联。

CrossInlineExample.kt
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 的标准库广泛使用内联函数进行集合操作。本例演示了它们内部的工作方式。

CollectionOperations.kt
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 标准库中的 mapfilterfold 等函数是内联的。这使得集合操作即使使用 lambda 表达式也有效。编译器内联函数和 lambda 表达式的主体。

何时不使用内联

此示例显示了内联函数可能没有益处甚至会损害性能的情况。

InlineDrawbacks.kt
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 函数在内联之前可能需要仔细考虑,因为存在二进制兼容性问题。

内联函数的最佳实践

来源

Kotlin 内联函数文档

本教程深入介绍了 Kotlin 的 inline 关键字,展示了它在性能和具体化类型方面的优势。我们探讨了各种场景,包括集合、属性和参数修饰符。正确使用内联函数可以使您的 Kotlin 代码更高效,同时保持清晰度。

作者

我的名字是 Jan Bodnar,我是一位充满激情的程序员,拥有多年的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我撰写了 1400 多篇文章和 8 本电子书。我拥有超过八年的编程教学经验。

列出 所有 Kotlin 教程