4.1 Kotlin 2.4.20 新特性

原文链接: https://kotlinlang.org/docs/whatsnew2420.html

4.1 Kotlin 2.4.20 新特性

阅读 Kotlin 2.4.20 版本说明,了解新的实验性特性,以及标准库、Native、Wasm、JS、Gradle、BTA 和 Kotlin 编译器方面的更新

发布于:2026 年 9 月 7 日

Kotlin 2.4.20 发布了!以下是本版本的亮点:

提示: 关于 Kotlin 发布周期的信息,请参阅 Kotlin 发布流程。

更新到 Kotlin 2.4.20

最新版本的 Kotlin 已包含在最新版本的 IntelliJ IDEA 和 Android Studio 中。

要更新到新的 Kotlin 版本,请确保把 IDE 更新到最新版本,并在构建脚本中把 Kotlin 版本改为 2.4.20。

新特性

稳定

Kotlin 2.2.20 引入了在 JVM 21 及更高版本上使用 invokedynamic 编译 when 表达式的实验性支持。

在 Kotlin 2.4.20 中,该特性已升级为稳定,并默认启用。

更多信息请参阅文档。

新特性

实验性

本版本提供以下尚处于预稳定阶段的特性,其中包括处于 Beta、Alpha 和 Experimental 状态的特性:

标准库

Kotlin 2.4.20 增加了对协程堆栈跟踪恢复的支持,并新增了用于检查集合元素相等性与唯一性的函数,以及 kotlin.test 断言函数的新重载。

支持协程堆栈跟踪恢复

实验性 标准库

Kotlin 2.4.20 为标准库新增了 StackTraceRecoverable 接口。这改善了与 kotlinx.coroutines 库的集成,因为它让你可以定义如何为堆栈跟踪恢复创建新的异常实例,而无需依赖 kotlinx.coroutines。

当一个协程抛出异常、另一个协程重新抛出它时,堆栈跟踪恢复有助于调试。它让你看到异常源自哪里,以及另一个协程在哪里重新抛出它。

kotlinx.coroutines 库通过创建一个带有额外协程堆栈跟踪信息的新异常实例来执行堆栈跟踪恢复。对于只接收异常消息、原因、两者或不接收参数的构造器,这一过程会自动完成。

如果异常构造器还有额外的必需参数(例如行号或错误码),请实现 StackTraceRecoverable 接口来定义 kotlinx.coroutines 库如何创建该异常的新实例。

要实现该接口,请重写 copyForStackTraceRecovery() 函数。在重写实现中,返回用于堆栈跟踪恢复的新异常实例;如果你不希望 kotlinx.coroutines 库复制该异常,则返回 null。

注意: StackTraceRecoverable 接口在所有目标上都可用,但 kotlinx.coroutines 库只在 JVM 上用它进行堆栈跟踪恢复。

这些 API 属于实验性,需要使用 @OptIn(ExperimentalStdlibCoroutineSupportApi::class) 注解选择启用。

下面是一个自定义异常的示例,它在为堆栈跟踪恢复创建新实例时会保留 line 属性:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import kotlin.coroutines.ExperimentalStdlibCoroutineSupportApi
import kotlin.coroutines.debug.StackTraceRecoverable

@OptIn(ExperimentalStdlibCoroutineSupportApi::class)
class FileEditException
// The implementation requires a private constructor
// to pass the cause to the IllegalStateException constructor
private constructor(
    val line: Int,
    private val detail: String,
    cause: Throwable?,
) : IllegalStateException("When editing line $line: $detail", cause),
    // Implements StackTraceRecoverable for stack trace recovery
    StackTraceRecoverable<FileEditException> {

    constructor(line: Int, detail: String) : this(line, detail, null)

    // Copies the line number and message details
    override fun copyForStackTraceRecovery(): FileEditException =
        FileEditException(line, detail, this)
    }

fun main() {
    val original = FileEditException(15, "Unexpected token")

    // Normally, you don't need to call this function directly unless you're testing its behavior
    // The kotlinx.coroutines library invokes it automatically during stack trace recovery
    val copy = original.copyForStackTraceRecovery()

    println(copy.message)
    // When editing line 15: Unexpected token

    println(copy.cause == original)
    // true
}

更多信息请参阅该特性的 KEEP。

欢迎在 YouTrack 上提供反馈。

用于检查集合元素相等性与唯一性的新函数

实验性 标准库

在 Kotlin 2.4.20 之前,如果你想检查集合元素是否全部互不相同或全部相等,就必须使用效率不高的代码写法。

Kotlin 2.4.20 引入了用来填补这一空白的实验性函数:

| 函数 | 检查内容 | | allDistinct() | 集合中的每个值都是唯一的。 | | allDistinctBy() | 每个对象所选属性的值都是唯一的。 | | allEqual() | 集合中的每个值都相同。 | | allEqualBy() | 每个对象所选属性的值都相同。 |

你可以在集合、序列和数组上使用这些函数。它们像其他集合操作一样,使用结构相等性比较元素。

这些函数属于实验性,需要使用 @OptIn(ExperimentalStdlibApi::class) 注解或 -opt-in=kotlin.ExperimentalStdlibApi 编译器选项选择启用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@OptIn(ExperimentalStdlibApi::class)
fun main() {
    data class Response(
        val participantId: String,
        val answer: String,
        val responseDate: String
    )

    val responses = listOf(
        Response("P001", "Yes", "2026-07-21"),
        Response("P002", "Maybe", "2026-07-21"),
        Response("P003", "No", "2026-07-21")
    )

    // Checks if all participants gave the same answer
    println(responses.allEqualBy { it.answer })
    // false

    // Checks for duplicate participants
    println(responses.allDistinctBy { it.participantId })
    // true

    // Checks if all responses were submitted on the same date
    println(responses.allEqualBy { it.responseDate })
    // true

    val answers = responses.map { it.answer }

    // Checks if answers are identical
    println(answers.allEqual())
    // false

    // Checks if answers are distinct
    println(answers.allDistinct())
    // true
}

欢迎在 KEEP 中提供反馈。

kotlin.test 断言函数的新重载

实验性 标准库

Kotlin 2.4.20 为 kotlin.test 断言函数新增了重载。它们接受一个 lambda,用来延迟生成错误消息,只在断言失败时才生成。

以前,像 assertTrue() 或 assertEquals() 这样的 kotlin.test 断言函数只接受预先格式化好的错误消息,即使断言成功、消息根本用不到,也会在每次断言时构建它。

新的重载让 kotlin.test API 与 JUnit 5 保持一致,通过 lambda 接受消息提供者,而不是普通字符串。这能提升性能,尤其是对 Power-assert 编译器插件而言——它会为断言生成详细的错误消息。

以下断言函数提供了新的重载:

| 函数 | 说明 | | assertTrue() / assertFalse() | 检查值是 true 还是 false。 | | assertEquals() / assertNotEquals() | 检查值是否相等。 | | assertSame() / assertNotSame() | 检查两个值是否引用同一个实例。 | | assertIs() / assertIsNot() | 检查值是否为指定类型。对于 assertIs(),函数会把它智能转换为该类型。 | | assertNull() | 检查值是否为 null。 | | assertContains() | 检查集合、数组、序列、区间或映射中是否存在该元素(键、字符、子字符串或正则表达式)。 | | assertContentEquals() | 检查集合、序列或数组是否以相同顺序包含相同的元素。 |

要使用新 API,请用 @OptIn(ExperimentalKotlinTestApi::class) 注解显式选择启用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import kotlin.test.ExperimentalKotlinTestApi
import kotlin.test.assertEquals
import kotlin.test.assertTrue

@OptIn(ExperimentalKotlinTestApi::class)
fun testValues(actual: Int, expected: Int, items: List<String>) {
    // The message is built only if the assertion fails
    assertTrue(actual > 0) { "Expected a positive value but got $actual" }

    // Avoids formatting the list unless the assertion fails
    assertEquals(expected, actual) { "Unexpected value for items: ${items.joinToString()}" }
}

更多信息请参阅该特性的 KEEP。

Kotlin/Native

Kotlin 2.4.20 为 Kotlin Multiplatform 项目中的 SwiftPM 依赖带来了 Package.swift 文件的自动生成、新的 Swift 导出特性(包括对密封类和跨语言继承的支持),以及改进的增量编译。

为 SwiftPM 依赖生成 Package.swift

Native

导出依赖 SwiftPM 包的 XCFramework 时,你必须发布生成的 SwiftPM 包,它才能被正确解析。为帮助解决这一问题,assembleSharedXCFramework Gradle 任务现在会生成一个 Package.swift 文件,与 XCFramework 一起分发。

详情请参阅 SwiftPM 导出页面。

新的 Swift 导出特性

Alpha Native

密封类

Kotlin 2.4.20 为 Swift 导出增加了对密封类和密封接口的支持。

以前,你必须为针对密封类型的每个 switch 语句编写 default 分支。现在,在 Kotlin 中定义的密封层级会映射为 Swift 枚举,从而支持穷尽的 switch 语句,并在 Xcode 中获得完整的自动补全。

Swift 导出会在每个密封类型上生成一个 sealedType() 方法。该方法返回一个 Swift 枚举,其成员与密封层级的直接子类相对应。你可以嵌套这些调用,以匹配层级中更深的层次。

例如,在 Kotlin 中声明一个带类层级的密封接口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Kotlin
sealed interface Shape

class Circle : Shape {
    override fun toString(): String = "Circle"
}

class Rectangle : Shape {
    override fun toString(): String = "Rectangle"
}

fun createCircle(): Shape = Circle()

在 Swift 一侧,你可以使用不带 default 分支的穷尽 switch:

1
2
3
4
5
6
7
8
// Swift
let shape = createCircle()

let name = switch shape.sealedType() {
    case let .circle(type): "It's a \(type.value)"
    case let .rectangle(type): "It's a \(type.value)"
}
// name == "It's a Circle"

由于 switch 是穷尽的,当密封层级中新增子类时编译器会警告你,这样你就能立即处理,而不必依赖 default 分支。

Swift 导出中的跨语言继承

Kotlin 2.4.20 在 Swift 导出中引入了跨语言继承支持。

该特性的一个常见用法是反向导入模式:你在 Kotlin 中定义契约,并在 Swift 一侧提供平台特有的实现。当你需要使用无法直接导入 Kotlin 的纯 Swift 库时,这尤其有用。

要实现这个模式,请声明一个供 Swift 实现继承的 Kotlin 父类,以及一个 Kotlin 接口。然后在 Swift 中实现该接口,并把 Swift 对象传给接受该接口的 Kotlin 函数。例如,对于 CryptoKit 库:

  1. 在 Kotlin 一侧,声明一个接口、一个接受该接口的函数,以及一个 open 基类:
1
2
3
4
5
6
7
8
   // Kotlin
   interface CryptoProvider {
       fun hashMD5(input: String): String
   }

   fun processHash(provider: CryptoProvider, input: String): String = provider.hashMD5(input)

   open class SwiftBase
  1. 在 Swift 一侧,继承导出的 SwiftBase 类,使用纯 Swift 库实现该接口,并把对象传回 Kotlin:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
   // Swift
   import CryptoKit

   final class IosCryptoProvider: SwiftBase, CryptoProvider {
       func hashMD5(input: String) -> String {
           guard let data = input.data(using: .utf8) else { return "failed" }
           return Insecure.MD5.hash(data: data).description
       }
   }

   let provider = IosCryptoProvider()

   // Calls the Kotlin function, which calls hashMD5() back in Swift
   print(processHash(provider: provider, input: "Hello, world!"))

当 Kotlin 收到一个 Swift 对象时,会把它当作普通接口的实现来处理,直接调用 Swift 代码。

关于 Swift 导出的更多细节,请参阅我们的文档。

改进 klib 产物的增量编译

Beta Native

Kotlin 2.4.20 对 klib 产物的增量编译做了稳定性方面的改进,该特性现已进入 Beta。

这项优化最早在 Kotlin 1.9.20 中引入,事实证明它能大幅缩短调试构建的编译时间。此后我们修复了若干缺陷并改进了性能。

要试用增量编译,请在 gradle.properties 文件中添加以下选项:

1
kotlin.incremental.native=true

我们正在积极收集反馈,并计划在接下来的 Kotlin 版本中为所有项目默认启用增量编译。如果你遇到任何问题,请在我们的问题跟踪器中报告。

Kotlin/Wasm

Kotlin 2.4.20 改变了 Kotlin/Wasm 处理 @JsFun 声明中顶层 require() 调用的方式,使伴生对象初始化顺序与 JVM 行为保持一致,缩小了函数式接口的二进制体积,引入了新的编译模式,并在 Kotlin Gradle 插件中为 wasmWasi 目标增加了对 Wasmtime 运行时的支持。

@JsFun 声明中顶层 require() 调用的变化

Wasm

当 @JsFun 声明使用顶层 require() 函数时,Kotlin/Wasm 现在会报告错误。

以前,编译器会在 import-object.mjs 文件中生成一个 require 变量,从而使 @JsFun 声明可以调用 require()。

这种行为无意中暴露了编译器的实现细节。为支持迁移,Kotlin/Wasm 移除了这个生成的 require 声明,编译器现在会对这类调用报错。例如:

1
2
3
// Reports an error
@JsFun("(mod) => require(mod)")
external fun loadModule(mod: String): JsAny

为这次变化做好准备,请把 @JsFun 声明中的顶层 require() 调用替换为 @JsModule 注解:

1
2
3
4
5
6
@JsModule("module")
external val module: Module

external interface Module {
    // Defines the expected module members
}

对于动态加载模块,请改用 import() 表达式。添加 /* webpackIgnore: true */ 魔法注释,防止 webpack 解析动态导入:

1
2
3
4
5
6
@JsFun("""
    ((module) => () => module)(
        await import(/* webpackIgnore: true */ "module")
    )
""")
private external fun loadModuleDynamically(): JsAny?

你也可以有条件地使用 import() 表达式。例如,只在 Node.js 中运行时加载模块:

1
2
3
4
5
6
7
8
@JsFun("""
    ((module) => () => module)(
        ((typeof process !== "undefined") && (process.release.name === "node"))
            ? await import(/* webpackIgnore: true */ "module")
            : null
    )
""")
private external fun loadNodeModule(): JsAny?

如果你的项目依赖某些需要顶层 require() 函数的库,可以把 require 添加为 globalThis 的属性作为变通方案:

1
2
3
4
5
6
7
@JsFun("""
    ((module) => {
        globalThis.require = module.default.createRequire(import.meta.url)
        return () => {}
    })(await import("node:module"))
""")
external fun defineRequire()

如果你遇到任何问题,请在我们的问题跟踪器中分享反馈。

改进伴生对象初始化顺序

Wasm

Kotlin/Wasm 现在会先初始化父类的伴生对象,再初始化子类的伴生对象,与 JVM 行为一致。以前初始化顺序可能相反,导致跨平台行为不一致。

这次更新提升了跨平台的一致性,减少了类初始化行为上的平台差异。它还使更深的继承层级中的伴生对象初始化得到正确处理,包括中间类没有声明伴生对象的情况。

Kotlin Gradle 插件对 Wasmtime 的支持

Wasm

Kotlin 2.4.20 在 Kotlin Gradle 插件中引入了对 Wasmtime 作为 wasmWasi 目标运行时的支持。

以前,wasmWasi 目标只支持 Node.js 运行时,运行 WASI 应用需要 JavaScript 引导。有了 Wasmtime 支持,你现在可以在独立的 WebAssembly 运行时上运行 Kotlin/Wasm 应用。

要把 Wasmtime 用作 wasmWasi 目标的运行时,请在 Gradle 构建文件中添加 wasmtime():

1
2
3
4
5
kotlin {
    wasmWasi {
        wasmtime()
    }
}

欢迎在 YouTrack 上提供反馈。

新的编译模式

Wasm

Kotlin 2.4.20 增加了选择 Kotlin/Wasm 编译模式的支持,包括新的多模块模式。以前编译器使用单体(monolith)编译模式,把项目和它的依赖一起编译,并生成单个二进制产物。这样编译器可以执行死代码消除并产出最小的输出。

现在你可以选择以下编译模式之一:

| 编译模式 | 编译方式 | 输出 | 优化行为 | | monolith(默认) | 把项目和它的依赖一起编译。 | 单个二进制文件 | 移除不可达声明,并对整个程序(包括依赖)应用优化。 | | multimodule-open-world | 独立编译每个模块,只重新编译发生变化的模块。 | 每个模块有各自独立的二进制文件 | 不应用跨模块优化,因此二进制文件更大。 | | multimodule-closed-world | 在一次调用中处理所有模块,只重新编译发生变化的模块。 | 相互依赖的独立二进制文件 | 移除不可达声明,但独立优化每个 Wasm 二进制文件。 |

要选择编译模式,请在 gradle.properties 文件中添加 kotlin.wasm.compilationMode 属性:

1
kotlin.wasm.compilationMode=multimodule-open-world

你也可以配置 Kotlin/Wasm,让开发构建使用闭世界多模块编译、生产构建使用单体编译。这样既能缩短开发期间的重新编译时间,又能为生产构建产出最小的输出。

要使用这种配置,请在 gradle.properties 文件中添加以下属性:

1
kotlin.wasm.compilationMode=multimodule-closed-world-only-in-dev

欢迎在 YouTrack 上提供反馈。

缩小 lambda 与函数式接口的二进制体积

Wasm

Kotlin 2.4.20 改变了 Kotlin/Wasm 编译 lambda 和函数式接口的方式。编译器不再生成单独的匿名类,而是生成函数并使用共享的基类。

用 KotlinConf 应用进行的测试表明,这一变化使 Wasm 二进制体积减少约 5–10%。

由于该变化引入了更多动态调用,可能会影响运行时性能。如果你遇到任何问题,请在我们的问题跟踪器中报告。

Kotlin/JS

Kotlin 2.4.20 改进了数据类的可导出性,引入了用于浏览器测试的新实验性 DSL,并增加了对把挂起 lambda 导出为 JavaScript async 函数的支持。

导出数据类上合成函数可导出性的一致性

Js

Kotlin 2.4.20 修复了一个问题:@JsExport.Ignore 注解无法正确应用于数据类属性。

以前,当你用 @JsExport 注解标记数据类时,编译器仍会因为自动生成的 copy() 和 componentN() 函数而报告数据类可导出性警告。即使构造器和属性都用 @JsExport.Ignore 显式标记为忽略,也依然如此。

例如,考虑一个导出到 JavaScript 的 Session 数据类,它同时引用了一个不打算导出的内部 DatabaseConnection 类型:

1
2
3
4
5
6
7
8
9
// Kotlin
// An internal type that isn't exported to JavaScript
class DatabaseConnection

@JsExport
data class Session @JsExport.Ignore constructor(
    val userId: String,
    @JsExport.Ignore val connection: DatabaseConnection,
)

现在问题已修复,编译器会把 @JsExport.Ignore 注解考虑在内,因此 Session 的合成 copy() 和 componentN() 函数不再触发关于未导出类型 DatabaseConnection 的警告。这与 @ConsistentCopyVisibility 和 @ExposedCopyVisibility 注解所引入的可见性规则保持一致。

用于浏览器测试的新 DSL

实验性 Js

Kotlin 2.4.20 引入了一个新的实验性 DSL,用于在浏览器环境中运行 Kotlin/JS 测试。

目前,Kotlin Gradle 插件使用 Karma 作为浏览器启动器,在不同浏览器中运行 JavaScript 测试。Karma 项目已经废弃两年了,这促使我们探索支持浏览器测试的其他方式。

新 DSL 旨在取代 Karma,在底层管理不同的工具,其中包括:

  • Playwright 作为浏览器驱动和分发管理器,支持 Chromium、Firefox 和 WebKit(Safari)浏览器引擎。
  • Mocha 作为测试运行器。
  • webpack 作为打包器(在未来的版本中将替换为 Vite)。

要试用用于浏览器测试的新 DSL,请在 Kotlin/JS 目标的 browser {} 中添加需要选择启用的 test {} 代码块:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import org.jetbrains.kotlin.gradle.ExperimentalJsTestDsl
import kotlin.time.Duration.Companion.seconds

kotlin {
    js {
        browser {
            // Add and configure the new test {} block
            @OptIn(ExperimentalJsTestDsl::class)
            test {
                // Configure default timeout for all runners
                timeout = 2.seconds
                // Configure headless mode using Gradle providers
                headless = providers
                    .environmentVariable("IS_IN_CI")
                    .map { it.toBoolean() }
                    .orElse(false)
                // Enable and configure Chromium test runner
                chromium {
                    // Override the common timeout option
                    timeout = 5.seconds
                    // Add extra launch arguments
                    launchArgs.add("--no-sandbox")
                }
                // Enable Firefox test runner
                firefox()
                // Enable WebKit test runner
                webkit()
                // Enable and configure an additional WebKit test runner
                webkit("noheadless") {
                    // Set up custom options
                    headless = false
                }
            }
        }
    }
}

用于浏览器测试的新 DSL 仍在积极开发中。欢迎在 YouTrack 上提供反馈。

更多信息请参阅在 Kotlin/JS 中运行测试。

支持把挂起 lambda 导出为 async 函数

实验性 Js

使用 Kotlin 2.4.20,你现在可以把挂起的 lambda 表达式导出为 JavaScript 的 async 函数。

以前,无法从 Kotlin/JS 库中导出包含挂起 lambda 的声明。现在 Kotlin 编译器会自动处理 Kotlin 的 suspend 函数与 JavaScript 原生 async/await 模型之间的桥接,这对于 Kotlin/TypeScript 混用的代码库很有用。

要启用该特性,请在 build.gradle.kts 文件中添加以下编译器选项:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
kotlin {
    js {
        compilations.all {
            compileTaskProvider.configure {
                compilerOptions {
                    freeCompilerArgs.add("-Xsuspend-lambda-exporting")
                }
            }
        }
    }
}

然后,用 @JsExport 标记相关的声明:

1
2
3
4
5
6
7
// Kotlin
@JsExport
class TaskRunner {
    suspend fun runTask(task: suspend () -> String): String {
        return task()
    }
}

在 TypeScript 一侧,这个挂起 lambda 会表现为普通的 async 函数:

1
2
3
4
5
6
// TypeScript
import { TaskRunner } from "..."

const runner = new TaskRunner();
const result = await runner.runTask(async () => "done");
console.log(result); // "done"

关于 @JsExport 注解的更多信息,请参阅我们的文档。

Gradle

Kotlin 2.4.20 与 Gradle 7.6.3 到 9.7.0 完全兼容。你也可以使用更高版本的 Gradle,最高可到最新的 Gradle 发行版。不过请注意,这样做可能会产生弃用警告,而且某些新的 Gradle 特性可能无法使用。

Kotlin 2.4.20 还改进了与 Problems API 的集成。

改进 Problems API 中的报告

Gradle

Kotlin 2.2.0 是第一个让 Kotlin Gradle 插件(KGP)与 Gradle 的 Problems API 集成的版本。Kotlin 2.4.0 增加了对把编译器消息写入 Kotlin/JVM 的 Problems API的支持。

Kotlin 2.4.20 在编译器传递给 Problems API 的信息中加入了编译器诊断 ID。它还会按这些 ID 对诊断分组,使定位编译问题的来源更容易。

从 Gradle 8.6 开始,KGP 默认启用这一集成。由于该 API 仍在演进,请使用最新的 Gradle 版本以享受最新的改进。

构建工具 API

Kotlin 2.4.20 为构建工具 API 增加了对 Kotlin/JS、Kotlin/Wasm 和 Kotlin 元数据的实验性支持。

支持 Kotlin/JS、Kotlin/Wasm 和 Kotlin 元数据

实验性 Bta

在 Kotlin 2.2.0 中,构建工具 API(BTA)已经可用于 Kotlin/JVM。Kotlin 2.4.20 朝着 BTA 的稳定化迈出了下一步,增加了对新目标的支持:Kotlin/JS、Kotlin/Wasm 和 Kotlin 元数据。

这让 Kotlin Gradle 插件与编译器的交互更加一致。在某些情况下,你还能享受到更快、更稳定的编译。

BTA 是一个通用 API,充当构建系统与 Kotlin 编译器生态之间的抽象层。它有助于在可用的构建工具中支持 Kotlin 特性并保持与 Kotlin 编译器的兼容性。

在 Kotlin 2.4.20 中,BTA 对这些新目标是以选择启用的方式提供的。要试用它,请在 gradle.properties 文件中添加相应的属性:

1
2
3
kotlin.wasm.runViaBuildToolsApi=true
kotlin.js.runViaBuildToolsApi=true
kotlin.metadata.runViaBuildToolsApi=true

从 Kotlin 2.5.0 开始,我们计划在 Kotlin/JS、Kotlin/Wasm 和 Kotlin 元数据中默认启用 BTA。

如果你对 BTA 提案感兴趣,或想分享反馈,请参阅这个 KEEP。

Kotlin 编译器

Kotlin 2.4.20 包含一项关于 Kotlin 运行器命令 kotlinr 变更的更新,并引入了实验性的 Kotlin 编译器本地镜像。

Kotlin 运行器命令从 kotlin 改为 kotlinr

编译器

kotlinr 命令取代 kotlin 成为 Kotlin 运行器命令,以避免与 Kotlin Toolchain 中的 kotlin 命令产生命名冲突。当你使用 kotlin 命令时,Kotlin 运行器还会给出警告,并建议改用 kotlinr。

本地镜像

实验性 编译器

Kotlin 2.4.20 带来了 Kotlin 编译器本地镜像的首个实验性版本。该本地镜像可以直接替代标准的 kotlinc 命令行工具,同时提供更快的启动速度和更高的性能。

要试用本地镜像,请从 GitHub Releases 下载构建产物。

该本地镜像还内置了以下编译器插件,你可以通过 -Xplugin 或 -Xcompiler-plugin 命令行选项使用它们:

关于 Kotlin 编译器本地镜像的更多信息,请参阅它的 README。

破坏性变更与弃用

本节重点介绍重要的破坏性变更和弃用。完整概览请参阅我们的兼容性指南。

  • 由于 Apple 正在放弃对其 32 位 watchOS 目标的支持,watchosArm32 Kotlin/Native 目标现已弃用。为确保与 Xcode 27 兼容,计划在 Kotlin 2.5.0 中移除它。
  • 从 Kotlin 2.4.20 开始,Kotlin/Native 编译器禁止在 public 内联函数中、或从另一个文件调用的 internal 内联函数中使用 AtomicFU 原子操作。
  • Kotlin 2.4.20 把 webpack 的 npm 依赖更新到 5.108.1。这可能以两种方式影响你的项目:
  • webpack 把内置的压缩器依赖从 terser-webpack-plugin 改为更通用的 minimizer-webpack-plugin。Terser 仍是默认的 JavaScript 压缩器,但如果你的项目直接配置或依赖 terser-webpack-plugin,可能需要更新其配置。
  • webpack 在判断 JavaScript 文件的模块类型时不再忽略 import.meta。如果存在 import.meta,webpack 会把这个文件当作 ES 模块,这可能会破坏同时使用 CommonJS 结构的文件。对于 Kotlin/JS,你可以通过 useEsModules() Gradle DSL 配置目标使用 ES 模块。Kotlin/Wasm 在大多数情况下无需额外配置即可正常工作。如果你在 Kotlin/Wasm 中遇到 import.meta 错误,请检查项目源码或直接/间接依赖是否使用了 import.meta。按需更新你自己的代码。如果是某个依赖导致的问题,请在有可用版本时把它更新到兼容版本,或向该库的维护者报告。
  • 从 Kotlin 2.4.20 开始,Kotlin/Wasm 弃用生成的 JavaScript wasmExports API。除 wasmExports.memory 之外,编译器禁止访问所有导出,而 wasmExports.memory 仍会在给出警告的前提下暂时可用。请使用 kotlin.wasm.unsafe.wasmMemory 属性访问模块的 WebAssembly.Memory 对象。

文档更新

自上一个版本以来,我们为 Kotlin 生态文档创建了新的页面和教程,并改版了现有内容:

  • 配置 iOS 交付流水线 —— 用 TeamCity 为 Kotlin Multiplatform iOS 应用搭建持续交付。
  • Compose Multiplatform 更新:
  • 弹出窗口 —— 了解如何在 Compose Multiplatform 中创建和配置弹出窗口。
  • 窗口与对话框 API v2 —— 探索 Compose Multiplatform 中用于管理桌面窗口和对话框的新 API。
  • 托盘与通知 —— 了解如何在桌面版 Compose Multiplatform 中把应用图标加入系统托盘并发送系统通知。
  • 菜单栏 —— 了解如何在桌面版 Compose Multiplatform 中为特定窗口创建菜单栏。
  • 拖放 —— 在 Compose Multiplatform 中实现拖放时处理平台特有的数据。
  • Liquid Glass 的 UIKit 替代方案 —— 探索一种使用 UIKit 导航而非 SwiftUI 来实现 Liquid Glass 的替代方案。
  • 面向 AI 代理的 MCP 服务器 —— 了解如何在 Compose Hot Reload 中使用 MCP 服务器,把 AI 代理接入你的开发工作流。
  • 用 Spring 实现缓存 —— 通过新的 Kotlin 示例了解如何为 Spring 应用添加缓存。
  • Exposed IntelliJ IDEA 插件 —— 了解如何在 IntelliJ IDEA 中使用代码补全、数据库感知检查和实时模板来使用 Exposed。
  • Kotlin 序列化 —— 了解如何序列化 Kotlin 数据、自定义 JSON 结构和类型表示,以及处理更高级的序列化场景。
  • Flow 与 Flow 操作符 —— 了解如何创建和收集冷流与热流、处理异常,以及使用各种 Flow 操作符。
  • 调试协程 —— 了解如何使用调试模式、堆栈跟踪恢复和调试代理在 JVM 上调试协程。
  • Lincheck —— 了解 Lincheck 中的模型检查如何工作,如何使用操作执行选项,以及如何验证测试结果。
  • kapt 编译器插件 —— 了解如何在 Gradle、Maven 和命令行编译器中配置 kapt 编译器插件。
  • Kotlin 项目中的代码质量工具 —— 探索用于分析 JVM 字节码和 Kotlin 代码的工具。
  • 在 Maven 中使用 Power-assert 插件 —— 了解如何使用 Power-assert 插件获得更详细的测试失败消息。
  • 使用 KSP 进行多轮处理 —— 探索 KSP 如何在多个处理轮次中工作,包括生成的文件、延迟符号和验证。
  • 不可表示类型 —— 了解 Kotlin 中的平台类型、捕获类型和交集类型。
  • 类型别名 —— 了解类型别名的作用域和可见性。
  • This 表达式 —— 了解隐式 this 如何解析,以及何时需要显式使用 this 来引用接收者。
  • 字符串 —— 了解字符串模板、常见字符串操作、构建字符串以及类型转换。
  • 包与导入 —— 了解如何使用包和导入组织 Kotlin 代码。