ZetCode

Kotlin noinline 关键字

最后修改于 2025 年 4 月 19 日

Kotlin 的内联函数通过内联 lambda 体来优化高阶函数。 noinline 修饰符可以控制哪些 lambda 不应该内联。 本教程深入探讨了 noinline

基本定义

Kotlin 中的 noinline 关键字标记一个 lambda 参数,当包含函数内联时,该参数不应内联。它与内联函数一起使用,以将特定的 lambda 排除在内联之外。 这在需要时将 lambda 保留为真实的对象。

基本 noinline 用法

noinline 最简单的用法是在内联函数中标记一个 lambda 参数。 这会阻止该特定 lambda 被内联,而其他 lambda 仍然会被内联。

BasicNoinline.kt
package com.zetcode

inline fun process(
    action: () -> Unit,
    noinline logger: (String) -> Unit
) {
    logger("Starting process")
    action()
    logger("Process completed")
}

fun main() {
    process(
        { println("Performing action") },
        { msg -> println("[LOG] $msg") }
    )
}

在这里,logger 被标记为 noinline,而 action 被内联。 logger lambda 仍然是一个函数对象,允许在需要时存储或传递它。 action lambda 在调用站点被内联。

为什么使用 noinline

使用 noinline 的主要原因是当你需要将 lambda 视为一个对象时。 内联 lambda 不能存储在变量中或作为对象传递。 noinline 保留了 lambda 的对象特性。

StoreLambda.kt
package com.zetcode

inline fun execute(
    task: () -> Unit,
    noinline callback: () -> Unit
) {
    val savedCallback = callback // Only possible with noinline
    task()
    savedCallback()
}

fun main() {
    execute(
        { println("Task executed") },
        { println("Callback invoked") }
    )
}

此示例显示将回调 lambda 存储在变量中。 如果没有 noinline,这是不可能的,因为内联 lambda 不作为对象存在。 noinline 修饰符使此操作有效。

混合 Inline 和 noinline

你可以在同一个函数中混合使用内联和非内联参数。 这可以让你精细地控制哪些 lambda 应该被优化,哪些应该保留为对象。

MixedInlining.kt
package com.zetcode

inline fun transform(
    data: List<Int>,
    noinline validator: (Int) -> Boolean,
    converter: (Int) -> Int
): List<Int> {
    return data.filter(validator).map(converter)
}

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5)
    val result = transform(numbers,
        { it % 2 == 0 }, // Not inlined
        { it * 2 }       // Inlined
    )
    println(result) // Output: [4, 8]
}

在这里,validator 没有内联(可以传递给 filter),而 converter 被内联了。 这演示了选择性优化,其中只有性能关键的 lambda 被内联。

noinline 与 Crossinline

noinline 可以与 crossinline 结合使用,以进一步控制 lambda 行为。 crossinline 阻止非局部返回,同时仍然内联 lambda。

CrossinlineNoinline.kt
package com.zetcode

inline fun runOperations(
    crossinline op: () -> Unit,
    noinline completion: () -> Unit
) {
    try {
        op()
    } finally {
        completion()
    }
}

fun main() {
    runOperations(
        { println("Operation running") },
        { println("Operation completed") }
    )
}

op lambda 是跨内联的(不允许非局部返回),而 completion 根本没有内联。 这种组合对于具有所需回调的控制流敏感操作非常有用。

noinline 在现实世界的场景中

在实际应用中,noinline 经常与大量使用回调的 API 一起使用。 它允许将回调传递给其他函数,同时仍然受益于性能关键部分的内联。

CallbackExample.kt
package com.zetcode

inline fun fetchData(
    noinline onSuccess: (String) -> Unit,
    noinline onError: (Exception) -> Unit
) {
    try {
        // Simulate data fetching
        val data = "Sample data"
        onSuccess(data)
    } catch (e: Exception) {
        onError(e)
    }
}

fun main() {
    fetchData(
        { data -> println("Received: $data") },
        { error -> println("Error: ${error.message}") }
    )
}

两个回调都被标记为 noinline,因为它们可能被异步调用或传递。 这种模式在网络请求处理程序或数据库操作中很常见,其中回调是必不可少的。

性能注意事项

虽然内联通过减少函数对象分配来提高性能,但 noinline 会用一些性能来换取灵活性。 在真正需要 lambda 对象的地方明智地使用它。

PerformanceTest.kt
package com.zetcode

inline fun benchmark(
    noinline setup: () -> Unit,
    action: () -> Unit,
    noinline teardown: () -> Unit
) {
    setup()
    val start = System.nanoTime()
    action()
    val duration = System.nanoTime() - start
    teardown()
    println("Operation took ${duration}ns")
}

fun main() {
    benchmark(
        { println("Setting up") },
        { List(1000) { it }.sum() },
        { println("Cleaning up") }
    )
}

在这里,性能关键的 action 被内联,而 setup/teardown 没有。 这优化了热点路径,同时保持了基础设施代码的灵活性。 在决定内联什么时,请衡量性能。

noinline 的局限性

noinline 参数有限制。 它们不能在非内联上下文中调用,例如嵌套对象。 它们也不能与某些仅内联操作一起使用,例如从封闭函数 return

Limitations.kt
package com.zetcode

inline fun problematic(
    noinline block: () -> Unit
) {
    val runnable = object : Runnable {
        override fun run() {
            block() // Error: Can't use noinline lambda here
        }
    }
    // ...
}

此代码将无法编译,因为 noinline lambda 不能在对象表达式中使用。 编译器强制执行此操作以保持 lambda 在不同上下文中的处理一致性。

noinline 的最佳实践

来源

Kotlin 内联函数文档

本教程深入介绍了 Kotlin 的 noinline 关键字,展示了它的用途和实际应用。 我们探讨了 noinline 至关重要的各种场景,并讨论了它的性能影响。 正确使用 noinline 有助于平衡灵活性和优化。

作者

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

列出 所有 Kotlin 教程