4.2 Kotlin 2.4.0 新特性

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

4.2 Kotlin 2.4.0 新特性

阅读 Kotlin 2.4.0 版本说明,了解新的语言特性,Kotlin Multiplatform、JVM、Native、JS 和 Wasm 的更新,以及 Gradle 和 Maven 的构建工具支持。

发布于:2026 年 7 月 14 日

关于缺陷修复版本 2.4.10 的详情,请参阅变更日志

Kotlin 2.4.0 发布了!以下是主要亮点:

你也可以通过这个视频了解这些更新的概览:

视频:What’s New in Kotlin 2.4

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

更新到 Kotlin 2.4.0

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

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

新特性

稳定

在之前的 Kotlin 版本中,有若干新特性是以实验性形式引入的。以下特性在 Kotlin 2.4.0 中已升级为稳定,因此你不再需要选择启用就能使用它们:

新特性

实验性

语言

Kotlin 2.4.0 把上下文参数、显式幕后字段和注解使用处目标特性升级为稳定。本版本还引入了上下文参数的显式上下文实参。

稳定特性

语言

Kotlin 2.2.0 和 2.3.0 以实验性形式引入了一些语言特性。我们很高兴地宣布,以下语言特性在本版本中已稳定:

查看 Kotlin 语言设计特性与提案的完整列表。

导入指令的最后一段不再产生弃用警告

语言

在以前的 Kotlin 版本中,导入已弃用的类时,弃用错误既会在调用处报告,也会在 import 指令本身报告。由于无法单独抑制 import 上的弃用错误,你可能只能通过抑制整个文件的弃用报告或使用星号导入来绕过它。

由于在导入被调用的符号时报告弃用信息在大多数情况下并没有用,Kotlin 2.4.0 不再在 import 指令的最后一段引用已弃用符号时发出警告。

更多信息请参阅 KT-30155。

上下文参数的显式上下文实参

实验性

语言

Kotlin 2.4.0 引入了上下文参数的显式上下文实参。

Kotlin 2.3.20 改变了上下文参数的重载解析。其结果是,仅靠上下文参数区分的那几个重载,其调用可能产生歧义。

现在你可以在调用处传入显式的上下文实参来消除这种歧义。

下面是一个示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class EmailSender
class SmsSender

context(emailSender: EmailSender)
fun sendNotification() {
    println("Sent email notification")
}

context(smsSender: SmsSender)
fun sendNotification() {
    println("Sent SMS notification")
}

context(defaultEmailSender: EmailSender, defaultSmsSender: SmsSender)
fun notifyUser() {

    // Selects the overload with the EmailSender context parameter
    sendNotification(emailSender = defaultEmailSender)

    // Selects the overload with the SmsSender context parameter
    sendNotification(smsSender = defaultSmsSender)
}

你也可以用显式上下文实参代替 context() 函数,以减少嵌套并让某些调用更易读。如果你需要在多次调用中使用相同的上下文实参,则仍应使用 context() 函数。

该特性属于实验性。要选择启用,请在构建文件中添加以下编译器选项:

Gradle

1
2
3
4
5
kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xexplicit-context-arguments")
    }
}

Maven

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<build>
    <plugins>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <configuration>
                <args>
                    <arg>-Xexplicit-context-arguments</arg>
                </args>
            </configuration>
        </plugin>
    </plugins>
</build>

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

支持集合字面量

实验性

语言

Kotlin 2.4.0 引入了对集合字面量的实验性支持。现在你可以用更简单、更简洁的方括号 [] 来创建集合。

例如:

1
2
3
4
5
6
7
8
9
fun main() {
    // Mutable list with explicit type declaration
    // val shapes: MutableList<String> = mutableListOf("triangle", "square", "circle")

    // Mutable list with brackets syntax
    val shapes: MutableList<String> = ["triangle", "square", "circle"]
    println(shapes)
    // [triangle, square, circle]
}

注意: 目前集合字面量还不能用于构造 Java 中定义的集合。更多信息请参阅 KT-80494。

如果编译器没有足够信息推断集合类型,它会默认使用 List 类型:

1
2
3
4
5
6
fun main() {
    val fruit = ["apple", "banana", "cherry"]

    println(fruit)
    // [apple, banana, cherry]
}

你也可以声明自定义的 operator fun of 函数,从而在你自己的类型上使用方括号语法。例如,假设你有下面这个 DoubleMatrix 类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class DoubleMatrix(vararg val rows: Row) {
    companion object {
        operator fun of(vararg rows: Row) = DoubleMatrix(*rows)
    }
    class Row(vararg val elements: Double) {
        companion object {
            operator fun of(vararg elements: Double) = Row(*elements)
        }
    }
}

就可以像这样创建 identityMatrix 类实例:

1
2
3
4
5
6
7
fun main() {
    val identityMatrix: DoubleMatrix = [
        [1.0, 0.0, 0.0],
        [0.0, 1.0, 0.0],
        [0.0, 0.0, 1.0],
    ]
}

在这个示例中,编译器把嵌套的集合字面量转换为对相应 operator fun of 函数的调用。编译器会递归解析这些调用,并使用期望的类型来选择正确的重载。

该特性属于实验性。要选择启用,请在构建文件中添加以下编译器选项:

Gradle

1
2
3
4
5
kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xcollection-literals")
    }
}

Maven

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<build>
    <plugins>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <configuration>
                <args>
                    <arg>-Xcollection-literals</arg>
                </args>
            </configuration>
        </plugin>
    </plugins>
</build>

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

改进的编译期常量

实验性

语言

Kotlin 2.4.0 为编译期常量带来了实验性改进,让数值类型和字符串类型方面的支持更加一致、更易使用。这些改进包括支持:

  • 无符号类型运算。
  • 字符串的标准库函数,例如 .lowercase()、.uppercase() 和 .trim() 函数。
  • 对枚举常量的 .name 属性以及 KCallable 接口求值。

为了让人们清楚哪些函数会在编译期求值,Kotlin 2.4.0 引入了 IntrinsicConstEvaluation 注解。有些函数会在编译期求值,但还没有该注解。后续版本会为其余函数补上这个注解。受支持函数的列表请参阅 KEEP 的附录。

该特性属于实验性。要选择启用,请在构建文件中添加以下编译器选项:

Gradle

1
2
3
4
5
kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xintrinsic-const-evaluation")
    }
}

Maven

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<build>
    <plugins>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <configuration>
                <args>
                    <arg>-Xintrinsic-const-evaluation</arg>
                </args>
            </configuration>
        </plugin>
    </plugins>
</build>

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

改进的高阶函数未使用结果检查

实验性

语言

Kotlin 2.4.0 引入了新的实验性 returnsResultOf() 契约,用来改进未使用返回值检查器。

该契约让检查器能够区分「可以忽略的未使用结果」和「有意义的高阶函数未使用结果」——后者会返回 lambda 的结果,例如 let 作用域函数。

警告: Kotlin 契约属于实验性。要选择启用,请在声明带契约的函数时添加 @OptIn(ExperimentalContracts::class) 注解。

要使用该特性,请在函数的契约中添加 returnsResultOf():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract

@OptIn(ExperimentalContracts::class)
inline fun <T, R> T.customLet(block: (T) -> R): R {
    contract {
        returnsResultOf(block)
    }
    return block(this)
}

下面是一个使用自定义 .customLet() 函数处理可空值的示例:

1
2
3
4
5
6
7
8
fun handleNullablePackageName(packageName: String?, builder: StringBuilder) {
    // The checker doesn't report a warning
    // because the return value of the append() function can be ignored
    packageName?.customLet { builder.append(it) }

    // The checker reports a warning because the returned string is unused
    packageName?.customLet { "kotlin.$it" }
}

未使用返回值检查器属于实验性,必须启用后才会报告未使用返回值。关于启用和配置该检查器的更多信息,请参阅未使用返回值检查器。

如何启用

returnsResultOf() 契约属于实验性。请注意,使用它会产生预发布二进制产物,较早版本的 Kotlin 编译器无法读取。要选择启用,请在构建文件中添加以下编译器选项:

Gradle

1
2
3
4
5
6
// build.gradle(.kts)
kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xallow-returns-result-of")
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

<build>
    <plugins>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <configuration>
                <args>
                    <arg>-Xallow-returns-result-of</arg>
                </args>
            </configuration>
        </plugin>
    </plugins>
</build>

新的 @IntroducedAt 注解,用于为可选参数生成基于版本的重载

实验性

语言

Kotlin 2.4.0 引入了 @IntroducedAt 注解,用于在向已发布的 API 添加新的可选参数时保持二进制兼容性。

以前,为函数添加可选参数通常需要使用 @JvmOverloads,而它可能生成超出需要的重载。另一种做法是保留旧签名作为隐藏的已弃用重载,以维持二进制兼容性。

有了 @IntroducedAt 注解,你可以为新添加的可选参数标注它们被引入的版本。编译器会利用这些信息自动生成相应的隐藏重载。

该注解属于实验性。要选择启用,请使用 @OptIn(ExperimentalVersionOverloading::class) 注解。

下面是一个示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@OptIn(ExperimentalVersionOverloading::class)
fun Button(
    label: String = "",
    color: Color = DefaultColor,
    @IntroducedAt("1.1") borderColor: Color = DefaultBorderColor,
    @IntroducedAt("1.2") borderStyle: Style = DefaultBorderStyle,
    @IntroducedAt("1.2") borderWidth: Int = 1,
    onClick: () -> Unit
) {
    // Function body
}

在这个示例中,编译器会为 Button() 函数较早的版本生成隐藏重载。

由于 @IntroducedAt 和 @JvmOverloads 都会生成重载,同时使用它们可能造成重载冲突。如果你同时使用这两个注解,编译器会发出警告。如果你抑制了该警告,编译器会优先采用由 @IntroducedAt 注解生成的重载。

标准库

Kotlin 2.4.0 稳定了通用 Kotlin 标准库中对 UUID 的支持。它还在 JVM 上新增了把无符号整数转换为 BigInteger 的扩展函数,并增加了对检查有序性的支持。

通用 Kotlin 标准库中稳定的 UUID API

标准库

Kotlin 2.0.20 引入了用于生成 UUID(通用唯一标识符)的类,并增加了在 Kotlin UUID 与 Java UUID 之间转换的支持。后续版本逐步改进了这一实验性特性,新增了以下支持:

在 Kotlin 2.4.0 中,kotlin.uuid.Uuid API 已成为稳定。唯一的例外是生成 V4 和 V7 UUID 的函数,它们仍属实验性,仍需要选择启用。

关于如何使用 UUID 的更多信息,请参阅 UUID。

支持检查有序性

标准库

Kotlin 2.4.0 新增了用于检查可迭代对象、数组和序列是否有序的扩展函数。

其中包括以下扩展函数:

  • .isSorted()
  • .isSortedDescending()
  • .isSortedWith(comparator)
  • .isSortedBy(selector)
  • .isSortedByDescending(selector)

你可以用这些扩展函数检查元素是否已经有序,而不必重新排序或自己编写辅助函数。如果元素符合指定的顺序,或者元素少于两个,它们返回 true,否则返回 false。这些函数一遇到不满足顺序的元素对就立即停止,因此对大量输入也很高效。

下面是一个用 .isSorted() 和 .isSortedBy() 函数检查有序性的示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
data class User(val name: String, val age: Int)

fun main() {
    val numbers = listOf(1, 2, 3, 4)
    println(numbers.isSorted())
    // true

    val users = listOf(
        User("Alice", 24),
        User("Bob", 31),
        User("Charlie", 29),
    )
    println(users.isSortedBy(User::age))
    // false
}

JVM 上把无符号整数转换为 BigInteger 的新 API

标准库

Kotlin 2.4.0 在 JVM 上引入了 UInt.toBigInteger() 和 ULong.toBigInteger() 扩展函数。

以前,把 UInt 和 ULong 值转换为 BigInteger 需要基于字符串的变通方案或自定义转换逻辑。从 Kotlin 2.4.0 开始,你可以用 .toBigInteger() 直接把无符号整数值转换为 BigInteger。

下面是一个示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fun main() {
    val unsignedLong = Long.MAX_VALUE.toULong() + 1uL
    val unsignedInt = UInt.MAX_VALUE

    println(unsignedLong.toBigInteger())
    // 9223372036854775808

    println(unsignedInt.toBigInteger())
    // 4294967295
}

新的映射回退函数,用于区分 null 值与缺失的键

实验性

标准库

Kotlin 2.4.0 为值可为空的映射新增了现有 .getOrElse() 和 .getOrPut() 映射扩展函数的变体。这些函数根据键读取值,或以默认值作为回退。对于值可为空的映射,新变体让你可以选择把存储的 null 值当作缺失的键还是当作已存在的值,并且在函数名中清楚地表达这一选择。

新的扩展函数包括:

  • .getOrElseIfNull(key, defaultValue) 和 .getOrPutIfNull(key, defaultValue):当键缺失或值为 null 时返回默认值,与现有的 .getOrElse() 和 .getOrPut() 函数类似。
  • .getOrElseIfMissing(key, defaultValue) 和 .getOrPutIfMissing(key, defaultValue):仅当映射中不包含指定键时才返回默认值。

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

下面这个示例演示了当键存在但值为 null 时,.getOrPutIfNull() 与 .getOrPutIfMissing() 的差别:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@OptIn(ExperimentalStdlibApi::class)
fun main() {
    val mapForNull = mutableMapOf<String, String?>("user" to null)
    val mapForMissing = mutableMapOf<String, String?>("user" to null)

    // Replaces the value if "user" has a null value
    mapForNull.getOrPutIfNull("user") { "default_user" }

    println(mapForNull)
    // {user=default_user}

    // Keeps the null value because "user" exists in the map
    mapForMissing.getOrPutIfMissing("user") { "default_user" }

    println(mapForMissing)
    // {user=null}
}

你也可以把 .getOrElseIfMissing() 和 .getOrPutIfMissing() 函数用于存储可空值的缓存。如果 defaultValue 返回 null,映射会把它存下来,并且不会再为同一个键调用 defaultValue。

下面是一个示例:

 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
data class Response(val body: String)

class Service {
    var queryCount = 0

    fun query(key: String): Response? {
        queryCount += 1
        return null
    }
}

@OptIn(ExperimentalStdlibApi::class)
fun main() {
    val service = Service()
    val cache = mutableMapOf<String, Response?>()

    fun getCachedResponseOrQuery(key: String): Response? =
        cache.getOrPutIfMissing(key) { service.query(key) }

    // Stores null because the cache doesn't contain "user"
    getCachedResponseOrQuery("user")

    println(cache)
    // {user=null}

    // Uses the cached null and doesn't query the service again
    getCachedResponseOrQuery("user")

    println(service.queryCount)
    // 1
}

欢迎在 YouTrack 上提供反馈。

Kotlin/JVM

Kotlin 2.4.0 支持新的 Java 版本,并默认启用元数据中的注解。

支持 Java 26

JVM

从 Kotlin 2.4.0 开始,编译器可以生成包含 Java 26 字节码的类。

默认启用元数据中的注解

JVM

Kotlin 2.2.0 中的 Kotlin Metadata JVM 库引入了读取存储在 Kotlin 元数据中的注解的支持。有了这项支持,Kotlin 编译器会把注解连同 JVM 字节码一起写入元数据,使 Kotlin Metadata JVM 库可以访问它们。这样一来,注解处理器和其他工具就能在元数据层面理解和操作这些注解,而不必使用反射或修改源码。

在 Kotlin 2.4.0 中,这项支持已默认启用。

Kotlin/Native

从 Kotlin 2.4.0 开始,Swift 导出升级为 Alpha。本版本还带来了对 Swift 包导入、Xcode 26.4 的支持,以及内存占用和垃圾回收方面的改进。

垃圾回收器默认启用并发标记

Native

在 Kotlin 2.0.20 中,Kotlin 团队引入了并发标记清除垃圾回收器(CMS GC)的实验性支持。在收集用户反馈并修复回归问题之后,我们现在准备从 Kotlin 2.4.0 开始默认启用 CMS。

此前垃圾回收器默认采用的并行标记并发清除(PMCS)方案,在 GC 标记堆中对象时必须暂停应用线程。相比之下,CMS 允许标记阶段与应用线程并发运行。

这显著缩短了 GC 暂停时间并提升了应用响应性,对延迟敏感型应用的性能尤为重要。CMS 已经在用 Compose Multiplatform 构建的 UI 应用基准测试中证明了它的有效性。

如果你遇到问题,可以切回 PMCS。为此,请在 gradle.properties 文件中设置以下二进制选项:

kotlin.native.binary.gc=pmcs

关于 Kotlin/Native 垃圾回收器的更多信息,请参阅我们的文档。

降低去虚化分析期间的内存占用

Native

以前,去虚化分析是 Kotlin/Native 编译器中内存占用最高的阶段之一。具体来说,链接发布(link release)任务会消耗过多内存,在大型项目中尤为明显。

Kotlin 2.4.0 引入了一些改进,有助于降低链接发布任务期间的峰值内存占用。

根据我们一位 EAP 用户的基准测试,改进后的去虚化分析让链接发布任务的内存占用减少了一半,至少节省了 13 GB。

支持 Xcode 26.4

Native

从 Kotlin 2.4.0 开始,Kotlin/Native 编译器支持 Xcode 26.4 —— Xcode 最新的稳定版本之一。

现在你可以更新 Xcode,获取最新的 API,继续开发面向 Apple 操作系统的 Kotlin 项目。

LLVM 更新到 21 版

Native

在 Kotlin 2.4.0 中,我们把 LLVM 从 19 版更新到 21 版。新版本包含性能改进,并有助于让 Kotlin/Native 编译器保持最新。

这次更新不应影响你的代码,但如果你遇到任何问题,请在我们的问题跟踪器中报告。

Apple 目标支持的变化

Native

Kotlin 2.4.0 提高了 Apple 目标的默认最低支持版本:

  • iOS 和 tvOS:从 14.0 提高到 15.0。
  • macOS:从 11.0 提高到 12.0。
  • watchOS:从 7.0 提高到 8.0。

如果你的项目需要支持低于默认值的版本,请在构建文件中使用 freeCompilerArgs 选项:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
kotlin {
    targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget>().configureEach {
        binaries.configureEach {
            freeCompilerArgs += "-Xoverride-konan-properties=minVersion.ios=14.0"
            freeCompilerArgs += "-Xoverride-konan-properties=minVersion.macos=11.0"
            freeCompilerArgs += "-Xoverride-konan-properties=minVersion.tvos=14.0"
            freeCompilerArgs += "-Xoverride-konan-properties=minVersion.watchos=7.0"
        }
    }
}

Swift 导出进入 Alpha,并改进了并发支持

Alpha

Native

从 Kotlin 2.4.0 开始,Kotlin 通过 Swift 导出与 Swift 的互操作性正式进入 Alpha!本版本对并发支持做了重大改进,为 Swift 导出加入了原生且直接的结构化并发,并支持把 kotlinx.coroutines 流导出到 Swift。

支持结构化并发

现在你可以从 Swift 无缝调用挂起的 Kotlin 代码。Kotlin 的 suspend 函数和挂起函数类型会导出为 Swift 惯用的 async 对应形式:

1
2
3
4
5
// Kotlin
suspend fun hello(): String {
    delay(1000)
    return "Hello Swift! This is Kotlin."
}
1
2
// Swift
let msg = try await hello()

把流类型导出到 Swift

这次更新还增加了把 kotlinx.coroutines 流导出到 Swift 的支持。kotlinx.coroutines 中的流表示可以并发发送和消费的异步数据流。它们常用于响应式编程模式,例如监听数据库更新、网络请求或 UI 事件。

以前,把 kotlinx.coroutines.flow 中的 Flow 接口暴露给 Swift 只能借助第三方方案。现在你可以开箱即用地把流导出为 Swift 的惯用对应形式:AsyncSequence。

该特性默认启用。你可以把任何带 Flow 类型的公开 API 导出到 Swift,并保留类型信息。例如:

1
2
3
// Kotlin
// Type String is preserved when exporting Flow
fun flowOfStrings(): Flow<String> = flowOf("hello", "any", "world")
1
2
3
4
5
6
7
// Swift
var actual: [String] = []

// Type String is correctly inferred from Kotlin
for try await element in flowOfStrings().asAsyncSequence() {
    actual.append(element)
}

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

Swift 包导入

实验性

Native

Kotlin Multiplatform 项目现在可以在 Gradle 配置中把 Swift 包声明为 iOS 应用的依赖:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// build.gradle.kts
kotlin {
    swiftPMDependencies {
        swiftPackage(
            url = url("https://github.com/firebase/firebase-ios-sdk.git"),
            version = from("12.11.0"),
            products = listOf(
                product("FirebaseAI"),
                product("FirebaseAnalytics"),
                ...
}

完整示例和更详细的信息请参阅 SwiftPM 导入。

如果你的项目依赖 CocoaPods,可以把现有配置迁移为使用 Swift 包。KMP 工具链考虑到了这种场景,会帮助你自动重新配置项目。详情请参阅我们的 CocoaPods 迁移指南。

Kotlin/Wasm

Kotlin 2.4.0 为 Kotlin/Wasm 默认启用增量编译,并引入了对 WebAssembly Component Model 的支持。

默认启用增量编译

Wasm

Kotlin/Wasm 在 Kotlin 2.1.0 中引入了增量编译。从 Kotlin 2.4.0 开始,它已稳定并默认启用。有了这个特性,编译器只重新构建受近期改动影响的文件,从而显著缩短构建时间。

要停用增量编译,请在项目的 local.properties 或 gradle.properties 文件中添加以下内容:

# gradle.properties
kotlin.incremental.wasm=false

如果你遇到任何问题,请在 YouTrack 中报告。

改进 Chrome DevTools 中内部变量的显示

Wasm

Kotlin 2.4.0 改进了在 Chrome DevTools 中调试 Kotlin/Wasm 的体验,让临时变量、合成变量和内部变量更容易与用户定义的变量区分开来。

Kotlin 编译器和诸如 Compose 之类的编译器插件都可能生成这些变量。它们现在默认使用 ~ 前缀,因此会被归为一组,并移动到变量列表末尾(Chrome DevTools 按名称排序)。

支持 WebAssembly Component Model

实验性

Wasm

Kotlin/Wasm 在 Kotlin 2.4.0 中更进一步,引入了对 WebAssembly Component Model 的实验性支持。该提案定义了一种通过标准化接口和类型从 Wasm 模块构建组件的方式。这种做法有助于 Wasm 从底层的二进制指令格式演变为可组合、与语言无关的可复用组件体系。它让 Kotlin/Wasm 能够走出浏览器。例如,Kotlin 和 WebAssembly 非常适合函数即服务(Function-as-a-Service,也称为 FaaS 或无服务器)应用。

要试用该特性,请查看一个用 wasi:http 构建的简单服务器。

使用 WebAssembly Component Model 的 Kotlin/Wasm

欢迎在 YouTrack 中分享反馈。

Kotlin/JS

Kotlin 2.4.0 进一步改进了到 JavaScript/TypeScript 的导出,包括支持导出值类、接口和类型型变,以及在 JS 代码内联时支持 ES2015 特性。

支持把值类导出到 JavaScript/TypeScript

Js

以前只有普通的 Kotlin 类可以导出到 JavaScript/TypeScript。Kotlin 2.4.0 取消了这个限制。现在你可以把 Kotlin 的内联值类导出为普通的 TypeScript 类。

要导出值类,请在 Kotlin 一侧用 @JsExport 注解标记它:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Kotlin
@JsExport
@JvmInline
value class Email(val address: String) {
    init { require(address.contains("@")) { "Invalid email" } }
}

@JsExport
class AuthService {
    suspend fun login(email: Email): String = ...
}

在 TypeScript 一侧,它看起来就像一个普通的类:

1
2
3
4
5
6
7
8
// TypeScript
import { AuthService, Email } from "..."
const auth = new AuthService();

console.log(await auth.login(new Email("jane@example.com")));
// "Welcome, jane@example.com!"
console.log(await auth.login(new Email("not-an-email")));
// "Invalid email"

更多信息请参阅 @JsExport 注解。

内联 JS 代码时支持 ES2015 特性

Js

从 Kotlin 2.4.0 开始,JavaScript 代码内联完整支持 ES2015 特性。

这对与第三方库的互操作很有用,也便于直接控制自动生成的应用代码。

现在你可以在 js() 调用中使用现代 JS 特性,包括:

  • const 和 let 变量声明
  • ES 类
  • 生成器
  • lambda(箭头函数)
  • 展开与剩余运算符
  • 模板字符串

请记住,js() 函数的参数必须是字符串常量,因为它会在编译期被解析并「原样」转换为 JavaScript 代码。例如,要内联展开运算符,可以这样写:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fun spreadExample(): dynamic = js("""
    const add = (a, b, c) => a + b + c;

    const nums = [1, 2, 3];
    const sum = add(...nums);

    const a = [1, 2, 3];
    const b = [...a, 4, 5, 6];

    return { sum, b: b };
""")

关于内联 JavaScript 代码的更多信息,请参阅我们的文档。

导出到 TypeScript 时保留类型型变

Js

以前,把类型导出到 TypeScript 时,泛型位置上的 Kotlin 型变信息会丢失。

在 Kotlin 2.4.0 中,型变注解在导出时会被保留,并映射到 TypeScript 的型变注解。

在你的 Kotlin 代码中定义泛型类型参数的型变:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Kotlin
// 'out' signals covariance (the interface only produces T)
interface Producer<out T> {
    fun produce(): T
}

// 'in' signals contravariance (the interface only consumes T)
interface Consumer<in T> {
    fun consume(item: T)
}

使用 Kotlin 2.4.0,生成的 TypeScript 输出会保留 in 和 out 关键字:

1
2
3
4
5
6
7
8
// Generated .d.ts
export interface Producer<out T> {
    produce(): T;
}

export interface Consumer<in T> {
    consume(item: T): void;
}

改进到 JavaScript/TypeScript 的接口导出

Js

Kotlin 2.4.0 让把 Kotlin 接口导出到 JavaScript/TypeScript 变得更加方便。

新的 @JsNoRuntime 注解去掉了实现 Kotlin 接口所需的元数据,从而可以直接映射为普通的 TypeScript 接口,与外部接口默认的行为类似。

要导出 Kotlin 接口,例如在 Kotlin Multiplatform 项目中,在公共代码中用 @JsNoRuntime 注解标记它:

1
2
3
4
5
6
7
// commonMain
import kotlin.js.JsNoRuntime

@JsNoRuntime
expect interface DataProcessor {
    fun process(data: String): Int
}

然后在 JS 专属源码中提供 actual 实现:

1
2
3
4
5
// jsMain
@JsNoRuntime
actual interface DataProcessor {
    actual fun process(data: String)
}

由于实现 Kotlin 接口所需的元数据被移除,该接口会映射为普通的 TypeScript 接口:

1
2
3
4
// Generated .d.ts
export interface DataProcessor {
    process(data: string): void;
}

@JsNoRuntime 注解只允许用在标准接口上,这样 TypeScript 才能把 Kotlin 接口当作普通 TypeScript 接口处理。因此,以下操作会被禁止:

  • is 和 as 类型检查。
  • 使用 ::class 语法的类引用。
  • 把接口作为具体化(reified)类型实参传递。

避免对外部接口使用 @JsNoRuntime 注解,因为这样会产生编译器警告。

放宽导出接口的限制

实验性

Js

Kotlin 2.4.0 朝着 @JsExport 的稳定化又迈进了一步,改进了 Kotlin 接口的导出方式。

现在你可以导出带嵌套类和具名伴生对象的 Kotlin 接口:

1
2
3
4
5
6
7
8
@JsExport
interface Identity {
    class Metadata(val tag: String)

    companion object Registry {
        val defaultTag = "GUEST"
    }
}

更多信息请参阅 @JsExport 注解。

Gradle

Kotlin 2.4.0 与 Gradle 7.6.3 到 9.5.0 完全兼容。你也可以使用更高版本的 Gradle,最高可到最新的 Gradle 发行版。不过请注意,这样做可能会产生弃用警告,而且某些新的 Gradle 特性可能无法使用。Kotlin 2.4.0 还带来了诸如跨平台一致的默认模块名、以及为 Kotlin/JVM 把编译器消息写入 Problems API 等改进。

最低支持的 AGP 版本提升到 8.5.2

Gradle

从 Kotlin 2.4.0 开始,最低支持的 Android Gradle 插件版本是 8.5.2。

跨平台一致的模块名

Gradle

在 Kotlin 2.4.0 之前,各平台的默认模块名各不相同。这种不一致可能造成命名冲突和解析问题。Kotlin 2.4.0 把各平台的默认名称统一为 {group}:{project_name}。

如果你需要把 JVM 模块名恢复为以前的版本,请在 Kotlin/JVM 项目的 build.gradle.kts 文件中添加:

1
2
3
kotlin {
    compilerOptions.moduleName(project.name)
}

对于多平台项目:

1
2
3
4
5
kotlin {
    jvm {
        compilerOptions.moduleName(project.name)
    }
}

Kotlin/JVM 的编译器消息写入 Problems API

Gradle

在 Kotlin 2.2.0 中,Kotlin Gradle 插件(KGP)开始把诊断信息报告给 Gradle 的 Problems API,以便在 Gradle 命令行和 IntelliJ IDEA 中提供一致的体验。

在 Kotlin 2.4.0 中,该插件还会为 Kotlin/JVM 把编译器消息写入 Problems API,让这个 API 更接近成为所有日志和消息的唯一来源。

Maven

Kotlin 2.4.0 通过支持 Maven Toolchains 以及 Java 与 JVM 目标版本自动对齐,让项目配置更加简单。

Java 与 JVM 目标版本自动对齐

Maven

为简化项目配置并避免兼容性问题,Kotlin Maven 插件现在会自动把 JVM 目标版本与项目中配置的 Java 编译器版本对齐。

这能确保 Kotlin 和 Maven 编译器以相同的字节码版本为目标,避免出现 Kotlin 生成的字节码与项目其余部分或预期部署环境不兼容的问题。

启用 <extensions> 选项后,你不需要再设置 kotlin.compiler.jvmTarget 或 kotlin.compiler.jdkRelease 选项。如果这两个都没有定义,Kotlin Maven 插件会按以下顺序自动解析 JVM 目标版本:

  1. 使用 maven.compiler.release 版本,它既可以定义在项目属性中,也可以定义在 maven-compiler-plugin 配置中。

在这种情况下,会为 Kotlin 编译器同时设置 jvmTarget 和 jdkRelease 两个编译器选项,从而把 API 限制到特定的 JDK 版本。

  1. 如果未设置 Maven release 版本,则使用 maven.compiler.target 版本。编译器 target 既可以定义在项目属性中,也可以定义在 maven-compiler-plugin 配置中。

在这种情况下,只设置 Kotlin 的 jvmTarget,API 不会被限制到特定的 JDK 版本。

这大大简化了 Kotlin 项目的配置,你的 pom.xml 文件可以写成这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<properties>
    <maven.compiler.release>17</maven.compiler.release>
    <kotlin.version>2.4.20</kotlin.version>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <version>${kotlin.version}</version>
            <extensions>true</extensions>
        </plugin>
    </plugins>
</build>

在构建过程中,插件会输出类似这样的消息:

[INFO] Using jvmTarget=17 (derived from maven.compiler.release=17)

注意: <extensions> 选项只检查项目级属性和全局的 maven-compiler-plugin 配置。它不会检查插件 <executions> 部分中定义的配置。

关于项目自动配置的更多信息,请参阅我们的文档。

支持 Maven Toolchains

Maven

Kotlin 2.4.0 为 Kotlin Maven 插件引入了对 Maven Toolchains 的支持。

该特性有助于管理构建中使用的 JDK 版本。借助 Maven Toolchains,你可以指定用于 Kotlin 编译的 JDK 版本,而不受运行 Maven 的 JVM 版本(在 JAVA_HOME 中设置)影响。当构建中配置了 maven-toolchains-plugin 时,Kotlin Maven 插件会像 Maven 编译器插件和其他 Maven 插件一样,自动采用所选的 JDK toolchain。这样你就可以配置一个统一的 toolchain,用它控制构建中所有插件(包括 Kotlin 编译)所使用的 JDK:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-toolchains-plugin</artifactId>
    <version>3.2.0</version>
    <executions>
        <execution>
            <goals>
                <goal>toolchain</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <toolchains>
            <jdk>
                <version>21</version>
            </jdk>
        </toolchains>
    </configuration>
</plugin>

请记住设置 JDK 版本的不同方式之间的优先级:

  1. kotlin-maven-plugin 配置中的 jdkHome。显式设置的 jdkHome 选项总是优先于 toolchain 版本。
  2. maven-toolchains-plugin 中的 JDK 版本。通过 Maven Toolchains 设置的 JDK 版本会覆盖 JAVA_HOME 路径中设置的 JDK 版本。
  3. JAVA_HOME 路径。

你也可以使用插件专用的 <jdkToolchain> 选项,直接在 kotlin-maven-plugin 的 toolchain 中设置 JDK 版本。与使用 maven-toolchains-plugin 相比,这个参数只影响 Kotlin 编译,对构建中的其他插件没有影响。

注意: 目前把 maven-toolchains-plugin 设置为使用特定 JDK 版本并不会影响 kotlin-maven-plugin 的 kapt 和 test-kapt goal。变通做法是在 JAVA_HOME 路径中设置所需版本。更多细节请参阅 KT-79897。

关于配置 Kotlin Maven 项目的更多信息,请参阅我们的文档。

构建工具 API

Bta

Kotlin 2.4.0 为构建工具 API(BTA)带来了多项改进。BTA:

  • 为大多数 JVM 和通用编译器选项引入了新的类型安全抽象。BTA 现在负责处理它们的格式,而不是由客户端处理,从而降低出错风险并提供额外一层协助。这一变化在运行时向后兼容,但可能破坏源码兼容性。
  • 现在可以在增量编译中跟踪非源码变化,例如配置不同的 Kotlin 版本或修改编译器选项。构建系统可以通过 BaseIncrementalCompilationConfiguration.TRACK_CONFIGURATION_INPUTS 选项控制这一行为。
  • 通过 AbiValidationToolchain 支持二进制兼容性验证,让其他构建系统更容易加入这一功能。
  • 引入新特性,让构建系统可以通过 CompilerMessageRenderer 接口和 JvmCompilationOperation 构建器自定义编译器消息的显示方式。
  • 引入用于配置 Kotlin 守护进程日志的新选项:
  • LOGS_PATH —— 守护进程日志文件所在目录。
  • LOGS_FILE_SIZE_LIMIT —— 日志文件的最大字节数。
  • LOGS_FILE_COUNT_LIMIT —— 保留的日志文件数量上限。

默认情况下,这些上限被设置为特定于 Kotlin 编译器版本的值。若要取消限制,构建工具必须把该选项设为 null。

构建系统可以在配置执行策略时设置该选项:

1
2
3
4
5
  val executionPolicy = kotlinToolchains.daemonExecutionPolicy {
      set(ExecutionPolicy.WithDaemon.LOGS_PATH, Paths("/var/log/kotlin-daemon"))
      set(ExecutionPolicy.WithDaemon.LOGS_FILE_SIZE_LIMIT, 10_485_760L)
      set(ExecutionPolicy.WithDaemon.LOGS_FILE_COUNT_LIMIT, 10)
  }

Kotlin 编译器

Kotlin 2.4.0 让同一模块中声明的内联函数在 .klib 编译期间的行为更加一致。

klib 编译期间同一模块内函数内联行为的一致性

编译器

以前,函数内联在不同 Kotlin 平台上的行为并不一致。JetBrains 团队正努力在所有受支持的平台上统一这一行为,以确保提供相同的兼容性保证。

在 Kotlin/JVM 上,函数内联发生在编译期。因此,用 Kotlin/JVM 编译器编译 Kotlin 源码时,生成的 class 文件字节码中不会保留内联函数调用,因为内联函数体已经被内联到调用处,其行为在编译期就已经固定。

相反,在 Kotlin/Native、Kotlin/JS 和 Kotlin/Wasm 上,函数内联并不发生在源码到 klib 的编译阶段,而只在生成二进制产物时发生。因此,内联函数的行为在 .klib 编译期间并未固定,.klib 库无法像 Kotlin/JVM 那样为内联函数提供相同的兼容性保证。

Kotlin 2.4.0 迈出了统一内联函数行为的第一步:在生成 .klib 产物时启用同一模块内的内联:

1
2
3
4
// Existing logging.klib library
inline fun logDebug(message: String) {
    println("[DEBUG] $message")
}
1
2
3
4
5
6
7
8
9
// Currently compiled App module
inline fun greetUser(name: String) {
    println("Hello, $name!")
}

fun main() {
    logDebug("App started") // Not inlined: declared in another module
    greetUser("Alice")      // Inlined: declared in the same module
}

编译为 .klib 后,代码大致如下:

1
2
3
4
5
6
// Pseudocode
fun main() {
    logDebug("App started")  // Not inlined, declared in another module
    val tmp0 = "Alice"
    println("Hello, $tmp0!") // Inlined from greetUser()
}

这意味着只有同一模块中声明的内联函数会在 .klib 编译期间被内联。其他函数(本例中就是另一个模块里的那个)会在生成平台特定二进制产物时内联。

如何启用

从 2.4.0 开始,同一模块内内联在 Kotlin/Native、Kotlin/JS 和 Kotlin/Wasm 上默认启用。

如果你在这个特性上遇到意外问题,可以用命令行中的以下编译器选项停用它:

1
-Xklib-ir-inliner=disabled

下一步是启用跨模块内联,以确保项目中所有内联函数都能一致地内联。这一变化计划在未来的 Kotlin 版本中推出,但你现在就可以用命令行中的以下编译器选项试用:

1
-Xklib-ir-inliner=full

欢迎在 YouTrack 中分享反馈并报告任何问题。

各 Kotlin 编译器之间一致的部分库链接

编译器

在 Kotlin 1.9.0 中,部分库链接(partial library linkage)在 Kotlin/Native 和 Kotlin/JS 编译器上默认启用,Kotlin/Wasm 在 Kotlin 2.0.0 中跟进。该特性实际上让编译器以与 Kotlin/JVM 一致的方式处理 Kotlin 库中的链接问题。

此后我们没有收到负面反馈,也没有发现有人在项目中停用部分链接。因此从 Kotlin 2.4.0 开始,部分链接始终启用,-Xpartial-linkage 编译器选项现已弃用。

所有 Kotlin 编译器的默认日志级别是 SILENT。编译期间不会报告链接问题。要在项目中改变这一行为,请在构建文件中设置 -Xpartial-linkage-loglevel 编译器选项:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// build.gradle.kts
kotlin {
    macosX64("native") {
        binaries.executable()

        compilations.configureEach {
            compilerOptions.configure {
                // To report linkage issues with the “info” log level:
                freeCompilerArgs.add("-Xpartial-linkage-loglevel=INFO")

                // To report issues as errors:
                freeCompilerArgs.add("-Xpartial-linkage-loglevel=ERROR")
            }
        }
    }
}
  • INFO 以 “info” 日志级别报告链接问题。
  • WARNING 在编译期报告警告,并把它们记录到编译日志中。
  • ERROR 在出现链接问题时让编译失败,并在编译日志中报告错误。需要更仔细地检查链接问题时可以使用这个选项。

如果你在这个特性上遇到问题,请在我们的问题跟踪器中报告。

Kotlin 编译器插件

在 Kotlin 2.4.0 中,Kotlin 编译器插件也获得了显著的更新。kapt 插件现在可以把不必要的注解处理器排除在编译类路径之外,Power-assert 插件则通过新的运行时库提供了更简单的配置方式。

kapt:从编译类路径中排除注解处理器

Kotlin 2.4.0 为注解处理器发现机制增加了对 includeCompileClasspath 配置选项的支持,与 Kotlin Gradle 插件类似。这个新选项让你可以把不必要的注解处理器排除在编译类路径之外。

要在构建文件中配置它,请在 kapt 插件的 <execution> 部分把 includeCompileClasspath 选项设为 false:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<execution>
    <id>kapt</id>
        <goals><goal>kapt</goal></goals>
        <configuration>

            <includeCompileClasspath>false</includeCompileClasspath>
            <sourceDirs>...</sourceDirs>
            <annotationProcessorPaths>...</annotationProcessorPaths>
        </configuration>
</execution>

或者,你也可以在 <properties> 部分通过 kapt.include.compile.classpath 达到同样效果:

1
2
3
<properties>
    <kapt.include.compile.classpath>false</kapt.include.compile.classpath>
</properties>

当该选项设为 false 时,未包含在 kapt 配置 <annotationProcessorPaths> 部分中的注解处理器会被排除在 kapt 处理之外。

如果没有设置 includeCompileClasspath,而 kapt 在编译类路径上发现了未在 <annotationProcessorPaths> 部分中显式定义的注解处理器,你会看到以下弃用警告:

1
[WARNING] Annotation processors discovery from compile classpath is deprecated. Set 'kapt.include.compile.classpath=false' to disable discovery.

关于 kapt 配置的更多信息,请参阅我们的文档。

Power-assert:新的运行时库

Kotlin 2.4.0 通过新的运行时库,让支持 Power-assert 的函数更容易被发现、也更容易配置。

以前,采用 Power-assert 需要复杂的构建配置和函数参数约定。从本版本开始,支持 Power-assert 的函数可以使用新的运行时库,直接与编译器插件的转换集成。

这为插件用户和库作者都带来了重大改进:

  • 新的 CallExplanation 数据结构提供关于调用处的详细信息。这使得断言失败时可以渲染更动态的示意图,并能更好地与外部工具集成。
  • 新的 @PowerAssert 注解让断言函数可以被编译器插件立即发现。这样你就可以在自己的库中开箱即用地支持 Power-assert。

提示: 可以使用我们的示例集合作为试验这些新特性的演练场。

更多信息请参阅我们的文档。

Compose 编译器

在 Kotlin 2.4.0 中,Compose 编译器提供了更一致的增量编译,并推进了若干特性标志的弃用周期。

内部声明增量编译的一致性

Compose 编译器

从 Kotlin 2.4.0 开始,Compose 编译器提供了更一致的增量编译。不同文件之间内部类型的稳定性现在会在运行时推断。这让 Compose 即使在没有重新编译类使用处的情况下,也能更新推断出的稳定性值。

副作用是:当某个 @Composable 函数把来自另一个文件的 internal 类用作参数时,你的产物体积可能增大。这是因为稳定性必须在运行时决定,编译器会为稳定和不稳定两种情况都编码执行路径。执行全应用优化的压缩工具(例如 R8)能够推断出多余的执行路径并将其消除,因此这种运行期稳定性的开销会被去除。

这次更新不会改变最终的稳定性值,因此 @Composable 函数的行为保持不变。

特性标志弃用

Compose 编译器

Kotlin 2.4.0 推进了若干已升级为稳定并默认启用的实验性特性标志的弃用周期:

  • StrongSkipping、IntrinsicRemember 及相关的 DSL 属性被提升为 DeprecationLevel.ERROR。它们将在 Kotlin 2.5.0 中移除。
  • OptimizeNonSkippingGroups 和 PausableComposition 现已弃用。它们计划在 Kotlin 2.6.0 中移除。

破坏性变更与弃用

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

文档更新

我们在 Kotlin 生态中做了以下文档改动: