当前位置:   article > 正文

kotlin 2.0 发布咯_kotlin2.0

kotlin2.0

发布日期:2024年5月21日

本文由deepseekAI提供翻译辅助

Kotlin 2.0.0 版本已经发布,新的 Kotlin K2 编译器 已经稳定!此外,还有其他一些亮点:

IDE 支持

支持 Kotlin 2.0.0 的 Kotlin 插件已包含在最新的 IntelliJ IDEA 和 Android Studio 中。您无需在 IDE 中更新 Kotlin 插件。您需要做的就是在构建脚本中更改 Kotlin 版本为 Kotlin 2.0.0。

  • 有关 IntelliJ IDEA 对 Kotlin K2 编译器支持的详细信息,请参阅 IDE 支持
  • 有关 IntelliJ IDEA 对 Kotlin 支持的更多详细信息,请参阅 Kotlin 版本

Kotlin K2 编译器

通往 K2 编译器的道路漫长,但现在 JetBrains 团队已准备好宣布其稳定化。在 Kotlin 2.0.0 中,新的 Kotlin K2 编译器默认使用,并且对于所有目标平台:JVM、Native、Wasm 和 JS 都是稳定的。新编译器带来了重大的性能改进,加快了新语言特性的开发,统一了 Kotlin 支持的所有平台,并为多平台项目提供了更好的架构。

JetBrains 团队通过成功编译来自选定用户和内部项目的 1000 万行代码,确保了新编译器的质量。18,000 名开发者和 80,000 个项目参与了稳定化过程,在他们的项目中尝试了新的 K2 编译器,并报告了他们发现的任何问题。

为了帮助迁移到新编译器的过程尽可能顺畅,我们创建了 K2 编译器迁移指南。该指南解释了编译器的许多好处,强调了您可能遇到的任何变化,并描述了如何在必要时回滚到以前的版本。

我们在一篇博客文章中探讨了 K2 编译器在不同项目中的性能。如果您想查看 K2 编译器的实际性能数据,并找到如何从您自己的项目中收集性能基准的说明,请查看它。

当前 K2 编译器的限制

在 Gradle 项目中启用 K2 会带来某些限制,这些限制可能会影响使用低于 8.3 版本的 Gradle 的项目,具体如下:

  • 编译来自 buildSrc 的源代码。
  • 编译包含在构建中的 Gradle 插件。
  • 如果它们在 Gradle 版本低于 8.3 的项目中使用,则编译其他 Gradle 插件。
  • 构建 Gradle 插件依赖项。

如果您遇到上述任何问题,您可以采取以下步骤来解决它们:

  • 设置 buildSrc、任何 Gradle 插件及其依赖项的语言版本:

    kotlin {
        compilerOptions {
            languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
            apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果您为特定任务配置语言和 API 版本,这些值将覆盖 compilerOptions 扩展设置的值。在这种情况下,语言和 API 版本不应高于 1.9。

    {type=“note”}

  • 将项目中的 Gradle 版本更新到 8.3 或更高版本。

智能类型转换改进

Kotlin 编译器可以在特定情况下自动将对象转换为类型,从而省去您必须显式转换它的麻烦。这称为智能类型转换。Kotlin K2 编译器现在比以前在更多场景中执行智能类型转换。

在 Kotlin 2.0.0 中,我们针对智能类型转换在以下领域进行了改进:

局部变量和更深层次的作用域

以前,如果变量在 if 条件中被评估为非 null,则该变量将被智能类型转换。关于该变量的信息随后将在 if 块的作用域内共享。

但是,如果您在 if 条件之外声明变量,则关于该变量的信息在 if 条件内不可用,因此无法进行智能类型转换。这种行为也出现在 when 表达式和 while 循环中。

从 Kotlin 2.0.0 开始,如果您在 ifwhenwhile 条件中使用之前声明变量,则编译器收集的关于该变量的任何信息都将在相应的块中可用于智能类型转换。

当您想要将布尔条件提取到变量中时,这可能很有用。然后,您可以为变量提供一个有意义的名称,这将提高代码的可读性,并使您能够在代码中稍后重用该变量。例如:

class Cat {
    fun purr() {
        println("Purr purr")
    }
}

fun petAnimal(animal: Any) {
    val isCat = animal is Cat
    if (isCat) {
        // 在 Kotlin 2.0.0 中,编译器可以访问
        // 关于 isCat 的信息,因此它知道
        // animal 已被智能类型转换为 Cat 类型。
        // 因此,可以调用 purr() 函数。
        // 在 Kotlin 1.9.20 中,编译器不知道
        // 智能类型转换,因此调用 purr()
        // 函数会触发错误。
        animal.purr()
    }
}

fun main() {
    val kitty = Cat()
    petAnimal(kitty)
    // Purr purr
}
  • 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

{kotlin-runnable=“true” kotlin-min-compiler-version=“2.0” id=“kotlin-smart-casts-k2-local-variables” validate=“false”}

使用逻辑 or 运算符的类型检查

在 Kotlin 2.0.0 中,如果您使用 or 运算符 (||) 组合对象的类型检查,则会将智能类型转换为其最接近的共同超类型。在此更改之前,智能类型转换总是转换为 Any 类型。

在这种情况下,您仍然必须在之后手动检查对象类型,然后才能访问其任何属性或调用其函数。例如:

interface Status {
    fun signal() {}
}

interface Ok : Status
interface Postponed : Status
interface Declined : Status

fun signalCheck(signalStatus: Any) {
    if (signalStatus is Postponed || signalStatus is Declined) {
        // signalStatus 被智能类型转换为共同超类型 Status
        signalStatus.signal()
        // 在 Kotlin 2.0.0 之前,signalStatus 被智能类型转换为类型 Any,因此调用 signal() 函数会触发未解析的引用错误。只有在进行另一次类型检查后,才能成功调用 signal() 函数:

        // check(signalStatus is Status)
        // signalStatus.signal()
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

共同超类型是对联合类型的一种近似联合类型在 Kotlin 中不受支持。

{type=“note”}

内联函数

在 Kotlin 2.0.0 中,K2 编译器以不同的方式处理内联函数,允许它结合其他编译器分析来确定是否可以安全地进行智能类型转换。

具体来说,内联函数现在被视为具有隐式的 callsInPlace 契约。这意味着传递给内联函数的任何 lambda 函数都在原地调用。由于 lambda 函数在原地调用,编译器知道 lambda 函数不能泄漏其函数体中包含的任何变量的引用。

编译器使用此知识以及其他编译器分析来决定是否可以安全地对捕获的变量进行智能类型转换。例如:

interface Processor {
    fun process()
}

inline fun inlineAction(f: () -> Unit) = f()

fun nextProcessor(): Processor? = null

fun runProcessor(): Processor? {
    var processor: Processor? = null
    inlineAction {
        // 在 Kotlin 2.0.0 中,编译器知道 processor 
        // 是一个局部变量,而 inlineAction() 是一个内联函数,因此 
        // 对 processor 的引用不能泄漏。因此,可以安全地进行智能类型转换。

        // 如果 processor 不为 null,则 processor 被智能类型转换
        if (processor != null) {
            // 编译器知道 processor 不为 null,因此不需要安全调用
            processor.process()

            // 在 Kotlin 1.9.20 中,您必须执行安全调用:
            // processor?.process()
        }

        processor = nextProcessor()
    }

    return processor
}
  • 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
具有函数类型的属性

在以前的 Kotlin 版本中,存在一个错误,即具有函数类型的类属性不会被智能类型转换。我们在 Kotlin 2.0.0 和 K2 编译器中修复了这种行为。例如:

class Holder(val provider: (() -> Unit)?) {
    fun process() {
        // 在 Kotlin 2.0.0 中,如果 provider 不为 null,则
        // provider 被智能类型转换
        if (provider != null) {
            // 编译器知道 provider 不为 null
            provider()

            // 在 1.9.20 中,编译器不知道 provider 不为 
            // null,因此会触发错误:
            // 引用具有可空类型 '(() -> Unit)?',使用显式的 '?.invoke()' 进行函数式调用
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

如果您重载 invoke 运算符,此更改也适用。例如:

interface Provider {
    operator fun invoke()
}

interface Processor : () -> String

class Holder(val provider: Provider?, val processor: Processor?) {
    fun process() {
        if (provider != null) {
            provider()
            // 在 1.9.20 中,编译器触发错误: 
            // 引用具有可空类型 'Provider?',使用显式的 '?.invoke()' 进行函数式调用
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
异常处理

在 Kotlin 2.0.0 中,我们对异常处理进行了改进,以便智能类型转换信息可以传递到 catchfinally 块中。此更改使您的代码更安全,因为编译器会跟踪您的对象是否具有可空类型。例如:

//sampleStart
fun testString() {
    var stringInput: String? = null
    // stringInput 被智能类型转换为 String 类型
    stringInput = ""
    try {
        // 编译器知道 stringInput 不为 null
        println(stringInput.length)
        // 0

        // 编译器拒绝之前对 stringInput 的智能类型转换信息。现在 stringInput 具有 String? 类型。
        stringInput = null

        // 触发异常
        if (2 > 1) throw Exception()
        stringInput = ""
    } catch (exception: Exception) {
        // 在 Kotlin 2.0.0 中,编译器知道 stringInput 
        // 可以为 null,因此 stringInput 保持可空。
        println(stringInput?.length)
        // null

        // 在 Kotlin 1.9.20 中,编译器表示不需要安全调用,但这是不正确的。
    }
}

//sampleEnd
fun main() {
    testString()
}
  • 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

{kotlin-runnable=“true” kotlin-min-compiler-version=“2.0” id=“kotlin-smart-casts-k2-exception-handling”}

增量和减量运算符

在 Kotlin 2.0.0 之前,编译器不理解在使用增量或减量运算符后对象的类型可以改变。由于编译器无法准确跟踪对象类型,您的代码可能会导致未解析的引用错误。在 Kotlin 2.0.0 中,这个问题已经得到解决:

interface Rho {
    operator fun inc(): Sigma = TODO()
}

interface Sigma : Rho {
    fun sigma() = Unit
}

interface Tau {
    fun tau() = Unit
}

fun main(input: Rho) {
    var unknownObject: Rho = input

    // 检查 unknownObject 是否继承自 Tau 接口
    if (unknownObject is Tau) {

        // 使用来自接口 Rho 的重载 inc() 运算符,
        // 它将 unknownObject 的类型智能类型转换为 Sigma。
        ++unknownObject

        // 在 Kotlin 2.0.0 中,编译器知道 unknownObject 具有类型
        // Sigma,因此可以成功调用 sigma() 函数。
        unknownObject.sigma()

        // 在 Kotlin 1.9.20 中,编译器认为 unknownObject 具有类型
        // Tau,因此调用 sigma() 函数是不允许的。

        // 在 Kotlin 2.0.0 中,编译器知道 unknownObject 具有类型
        // Sigma,因此调用 tau() 函数是不允许的。
        unknownObject.tau()
        // 未解析的引用 'tau'

        // 在 Kotlin 1.9.20 中,编译器错误地认为 
        // unknownObject 具有类型 Tau,tau() 函数可以 
        // 成功调用。
    }
}
  • 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
  • 37
  • 38
  • 39

{kotlin-runnable=“true” kotlin-min-compiler-version=“2.0” id=“kotlin-smart-casts-k2-increment-decrement-operators” validate=“false”}

Kotlin 多平台改进

在 Kotlin 2.0.0 中,我们在 K2 编译器中针对 Kotlin 多平台在以下方面进行了改进:

编译期间公共和平台源代码的分离

以前,Kotlin 编译器的设计阻止了它在编译时将公共和平台源代码集保持分离。因此,公共代码可以访问平台代码,导致不同平台之间的行为不同。此外,公共代码的一些编译器设置和依赖项曾经泄漏到平台代码中。

在 Kotlin 2.0.0 中,我们对新的 Kotlin K2 编译器的实现包括了对编译方案的重新设计,以确保公共和平台源代码集之间的严格分离。当您使用 预期和实际函数 时,这一变化最为明显。以前,公共代码中的函数调用可能会解析为平台代码中的函数。例如:

公共代码平台代码
fun foo(x: Any) = println("公共 foo")

fun exampleFunction() {
    foo(42)
}
  • 1
  • 2
  • 3
  • 4
  • 5
// JVM
fun foo(x: Int) = println("平台 foo")

// JavaScript
// JavaScript 平台上没有 foo() 函数的重载
  • 1
  • 2
  • 3
  • 4
  • 5

在这个例子中,公共代码在不同的平台上运行时行为不同:

  • 在 JVM 平台上,公共代码中调用 foo() 函数会导致调用平台代码中的 foo() 函数,输出为 平台 foo
  • 在 JavaScript 平台上,公共代码中调用 foo() 函数会导致调用公共代码中的 foo() 函数,输出为 公共 foo,因为在平台代码中没有这样的函数可用。

在 Kotlin 2.0.0 中,公共代码无法访问平台代码,因此两个平台都能成功将 foo() 函数解析为公共代码中的 foo() 函数:公共 foo

除了跨平台行为的一致性改进之外,我们还努力修复了 IntelliJ IDEA 或 Android Studio 与编译器之间存在冲突行为的情况。例如,当您使用 预期和实际类 时,会发生以下情况:

公共代码平台代码
expect class Identity {
    fun confirmIdentity(): String
}

fun common() {
    // 在 2.0.0 之前,
    // 它触发仅 IDE 的错误
    Identity().confirmIdentity()
    // RESOLUTION_TO_CLASSIFIER : 预期的类
    // Identity 没有默认构造函数。
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
actual class Identity {
    actual fun confirmIdentity() = "预期类函数: jvm"
}
  • 1
  • 2
  • 3

在这个例子中,预期的类 Identity 没有默认构造函数,因此无法在公共代码中成功调用。以前,只有 IDE 报告错误,但代码仍然可以在 JVM 上成功编译。然而,现在编译器正确报告错误:

预期的类 '预期类 Identity : Any' 没有默认构造函数
  • 1
当解析行为不改变时

我们仍在迁移到新的编译方案的过程中,因此当您调用不在同一源代码集中的函数时,解析行为仍然相同。当您在公共代码中使用多平台库的重载时,您会注意到这种差异。

假设您有一个库,它有两个具有不同签名的 whichFun() 函数:

// 示例库

// MODULE: 公共
fun whichFun(x: Any) = println("公共函数")

// MODULE: JVM
fun whichFun(x: Int) = println("平台函数")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

如果您在公共代码中调用 whichFun() 函数,库中最相关的参数类型的函数将被解析:

// 为 JVM 目标使用示例库的项目

// MODULE: 公共
fun main() {
    whichFun(2)
    // 平台函数
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

相比之下,如果您在同一源代码集中声明 whichFun() 的重载,则将解析公共代码中的函数,因为您的代码无法访问特定于平台的版本:

// 未使用示例库

// MODULE: 公共
fun whichFun(x: Any) = println("公共函数")

fun main() {
    whichFun(2)
    // 公共函数
}

// MODULE: JVM
fun whichFun(x: Int) = println("平台函数")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

与多平台库类似,由于 commonTest 模块位于单独的源代码集中,因此它仍然可以访问特定于平台的代码。因此,对 commonTest 模块中的函数的调用解析表现出与旧编译方案相同的行为。

将来,这些剩余情况将更一致地与新的编译方案保持一致。

预期和实际声明的不同可见性级别

在 Kotlin 2.0.0 之前,如果您在 Kotlin 多平台项目中使用 预期和实际声明,它们必须具有相同的 可见性级别。Kotlin 2.0.0 现在还支持不同的可见性级别,但仅当实际声明比预期声明宽松时。例如:

expect internal class Attribute // 可见性为 internal
actual class Attribute          // 默认可见性为 public,
                                // 这更宽松
  • 1
  • 2
  • 3

同样,如果您在实际声明中使用 类型别名基础类型的可见性应与预期声明相同或更宽松。例如:

expect internal class Attribute                 // 可见性为 internal
internal actual typealias Attribute = Expanded

class Expanded                                  // 默认可见性为 public,
                                                // 这更宽松
  • 1
  • 2
  • 3
  • 4
  • 5

Kotlin K2 编译器支持的编译器插件

目前,Kotlin K2 编译器支持以下 Kotlin 编译器插件:

此外,Kotlin K2 编译器还支持:

如果您使用任何其他编译器插件,请检查其文档以查看它们是否与 K2 兼容。

{type=“tip”}

实验性的 Kotlin Power-assert 编译器插件

Kotlin Power-assert 插件是实验性的。它可能会随时更改。

{type=“warning”}

Kotlin 2.0.0 引入了一个实验性的 Power-assert 编译器插件。该插件通过在失败消息中包含上下文信息来改善编写测试的体验,使调试更容易和更高效。

开发人员通常需要使用复杂的断言库来编写有效的测试。Power-assert 插件通过自动生成包含断言表达式中间值的失败消息来简化这一过程。这有助于开发人员快速理解为什么测试失败。

当测试中的断言失败时,改进的错误消息显示断言中所有变量和子表达式的值,清楚地显示哪个部分的条件导致了失败。这对于检查多个条件的复杂断言特别有用。

要在项目中启用该插件,请在您的 build.gradle(.kts) 文件中配置它:

plugins {
    kotlin("multiplatform") version "2.0.0"
    kotlin("plugin.power-assert") version "2.0.0"
}

powerAssert {
    functions = listOf("kotlin.assert", "kotlin.test.assertTrue")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
plugins {
    id 'org.jetbrains.kotlin.multiplatform' version '2.0.0'
    id 'org.jetbrains.kotlin.plugin.power-assert' version '2.0.0'
}

powerAssert {
    functions = ["kotlin.assert", "kotlin.test.assertTrue"]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

了解更多关于 Kotlin Power-assert 插件的文档

如何启用 Kotlin K2 编译器

从 Kotlin 2.0.0 开始,Kotlin K2 编译器默认启用。无需额外操作。

在 Kotlin Playground 中尝试 Kotlin K2 编译器

Kotlin Playground 支持 2.0.0 版本。试试看!

IDE 中的支持

默认情况下,IntelliJ IDEA 和 Android Studio 仍然使用之前的编译器进行代码分析、代码完成、突出显示和其他与 IDE 相关的功能。要在您的 IDE 中获得完整的 Kotlin 2.0 体验,请启用 K2 Kotlin 模式。

在您的 IDE 中,转到 设置 | 语言与框架 | Kotlin 并选择 启用基于 K2 的 Kotlin 插件 选项。IDE 将使用其 K2 Kotlin 模式分析您的代码。

K2 Kotlin 模式处于 Alpha 阶段,从 2024.1 开始可用。代码突出显示和代码完成的性能和稳定性已显著提高,但并非所有 IDE 功能都已支持。

{type=“warning”}

启用 K2 模式后,您可能会注意到由于编译器行为的改变,IDE 分析存在差异。了解新的 K2 编译器与之前编译器的不同之处,请参阅我们的 迁移指南

  • 了解更多关于 K2 Kotlin 模式的信息,请参阅 我们的博客
  • 我们正在积极收集有关 K2 Kotlin 模式的反馈。请在我们的 公共 Slack 频道 分享您的想法。

对新的 K2 编译器留下您的反馈

我们非常感谢您可能有的任何反馈!

Kotlin/JVM

此版本带来了以下更改:

使用 invokedynamic 生成 lambda 函数

Kotlin 2.0.0 引入了一种使用 invokedynamic 生成 lambda 函数的新默认方法。与传统的匿名类生成相比,这一变化减少了应用程序的二进制大小。

自第一个版本以来,Kotlin 一直将 lambda 生成作为匿名类。然而,从 Kotlin 1.5.0 开始,通过使用 -Xlambdas=indy 编译器选项,invokedynamic 生成选项已经可用。在 Kotlin 2.0.0 中,invokedynamic 已成为 lambda 生成的默认方法。这种方法产生更轻的二进制文件,并使 Kotlin 与 JVM 优化保持一致,确保应用程序从 JVM 性能的持续和未来改进中受益。

目前,与普通 lambda 编译相比,它有三个限制:

  • 编译为 invokedynamic 的 lambda 不可序列化。
  • 实验性的 reflect() API 不支持由 invokedynamic 生成的 lambda。
  • 对这样的 lambda 调用 .toString() 会产生一个不太易读的字符串表示:
fun main() {
    println({})

    // 使用 Kotlin 1.9.24 和反射,返回
    // () -> kotlin.Unit
    
    // 使用 Kotlin 2.0.0,返回
    // FileKt$$Lambda$13/0x00007f88a0004608@506e1b77
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

要保留生成 lambda 函数的遗留行为,您可以:

  • 使用 @JvmSerializableLambda 注解特定的 lambda。
  • 使用编译器选项 -Xlambdas=class 来生成模块中所有 lambda 的遗留方法。

kotlinx-metadata-jvm 库现已稳定

在 Kotlin 2.0.0 中,kotlinx-metadata-jvm 库已变为 稳定。现在该库已更改为 kotlin 包和坐标,您可以找到它作为 kotlin-metadata-jvm(没有 “x”)。

以前,kotlinx-metadata-jvm 库有自己的发布计划和版本。现在,我们将作为 Kotlin 发布周期的一部分构建和发布 kotlin-metadata-jvm 更新,并具有与 Kotlin 标准库相同的向后兼容性保证。

kotlin-metadata-jvm 库提供了一个 API,用于读取和修改由 Kotlin/JVM 编译器生成的二进制文件的元数据。

Kotlin/Native

此版本带来了以下更改:

在 Apple 平台上使用 signposts 监控 GC 性能

以前,只能通过查看日志来监控 Kotlin/Native 的垃圾收集器 (GC) 的性能。然而,这些日志并未与 Xcode Instruments 集成,Xcode Instruments 是一个流行的工具包,用于调查 iOS 应用性能问题。

自 Kotlin 2.0.0 起,GC 使用 Instruments 中可用的 signposts 报告暂停。Signposts 允许在应用程序中进行自定义日志记录,因此现在在调试 iOS 应用性能时,可以检查 GC 暂停是否对应于应用程序冻结。

了解更多关于 GC 性能分析的信息,请参阅 文档

解决与 Objective-C 方法的冲突

Objective-C 方法可以有不同的名称,但参数的数量和类型相同。例如,locationManager:didEnterRegion:locationManager:didExitRegion:。在 Kotlin 中,这些方法具有相同的签名,因此尝试使用它们会触发冲突的重载错误。

以前,您必须手动抑制冲突的重载以避免此编译错误。为了改进 Kotlin 与 Objective-C 的互操作性,Kotlin 2.0.0 引入了新的 @ObjCSignatureOverride 注解。

该注解指示 Kotlin 编译器在继承自 Objective-C 类的几个具有相同参数类型但不同参数名称的方法的情况下忽略冲突的重载。

应用此注解也比一般的错误抑制更安全。此注解只能用于重写 Objective-C 方法的情况,这些方法得到支持并经过测试,而一般的抑制可能会隐藏重要的错误并导致代码无声地损坏。

更改 Kotlin/Native 中编译器参数的日志级别

在此版本中,Kotlin/Native Gradle 任务(如 compilelinkcinterop)的编译器参数的日志级别已从 info 更改为 debug

使用 debug 作为默认值,日志级别与其他 Gradle 编译任务一致,并提供详细的调试信息,包括所有编译器参数。

在 Kotlin/Native 中显式添加标准库和平台依赖

Kotlin/Native 编译器过去常常隐式解析标准库和平台依赖,这导致了 Kotlin Gradle 插件在不同 Kotlin 目标之间的工作方式不一致。

现在,每个 Kotlin/Native Gradle 编译都会通过 compileDependencyFiles 编译参数显式地在编译时库路径中包含标准库和平台依赖。

在 Gradle 配置缓存中的任务错误

自 Kotlin 2.0.0 起,您可能会遇到配置缓存错误,其消息指示:不支持在执行时调用 Task.project

此错误出现在诸如 NativeDistributionCommonizerTaskKotlinNativeCompile 等任务中。

然而,这是一个误报错误。根本问题是存在与 Gradle 配置缓存不兼容的任务,例如 publish* 任务。

这种差异可能不会立即显现,因为错误消息暗示了不同的根本原因。

由于错误报告中并未明确指出确切原因,Gradle 团队已经在处理此问题以修复报告

Kotlin/Wasm

Kotlin 2.0.0 改进了与 JavaScript 的性能和互操作性:

默认使用 Binaryen 优化生产构建

Kotlin/Wasm 工具链现在在所有项目的生产编译过程中应用 Binaryen 工具,与之前的手动设置方法相反。据我们估计,它应该会提高您项目的运行时性能和二进制大小。

此更改仅影响生产编译。开发编译过程保持不变。

{type=“note”}

支持命名导出

以前,从 Kotlin/Wasm 导出的所有声明都使用默认导出导入到 JavaScript 中:

// JavaScript:
import Module from "./index.mjs"

Module.add()
  • 1
  • 2
  • 3
  • 4

现在,您可以按名称导入每个带有 @JsExport 标记的 Kotlin 声明:

// Kotlin:
@JsExport
fun add(a: Int, b: Int) = a + b
  • 1
  • 2
  • 3
// JavaScript:
import { add } from "./index.mjs"
  • 1
  • 2

命名导出使得在 Kotlin 和 JavaScript 模块之间共享代码变得更加容易。它们提高了可读性,并帮助您管理模块之间的依赖关系。

在带有 @JsExport 的函数中支持无符号原始类型

从 Kotlin 2.0.0 开始,您可以在外部声明和带有 @JsExport 注解的函数中使用 无符号原始类型,这使得 Kotlin/Wasm 函数可以在 JavaScript 代码中使用。

这有助于缓解之前阻止 无符号原始类型 直接在导出和外部声明中使用的限制。现在,您可以导出带有无符号原始类型作为返回类型或参数类型的函数,并使用返回或使用无符号原始类型的外部声明。

有关 Kotlin/Wasm 与 JavaScript 互操作性的更多信息,请参阅 文档

在 Kotlin/Wasm 中生成 TypeScript 声明文件

在 Kotlin/Wasm 中生成 TypeScript 声明文件是 实验性的。它可能会在任何时候被删除或更改。

{type=“warning”}

在 Kotlin 2.0.0 中,Kotlin/Wasm 编译器现在能够从您的 Kotlin 代码中的任何 @JsExport 声明生成 TypeScript 定义。这些定义可以被 IDE 和 JavaScript 工具用来提供代码自动完成、帮助类型检查,并使得在 JavaScript 中包含 Kotlin 代码变得更加容易。

Kotlin/Wasm 编译器收集任何 顶层函数 并自动在 .d.ts 文件中生成 TypeScript 定义。

要生成 TypeScript 定义,请在您的 build.gradle(.kts) 文件中的 wasmJs {} 块中添加 generateTypeScriptDefinitions() 函数:

kotlin {
    wasmJs {
        binaries.executable()
        browser {
        }
        generateTypeScriptDefinitions()
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

支持捕获 JavaScript 异常

以前,Kotlin/Wasm 代码无法捕获 JavaScript 异常,这使得处理源自 JavaScript 侧程序的错误变得困难。

在 Kotlin 2.0.0 中,我们实现了在 Kotlin/Wasm 中捕获 JavaScript 异常的支持。这个实现允许您使用 try-catch 块,以及 ThrowableJsException 等特定类型,来适当地处理这些错误。

此外,finally 块也能正确工作,它有助于执行无论是否发生异常都需要执行的代码。虽然我们引入了捕获 JavaScript 异常的支持,但当 JavaScript 异常发生时,不会提供额外的信息,如调用堆栈。然而,我们正在致力于这些实现

新的异常处理提案现在作为选项支持

在此版本中,我们为 Kotlin/Wasm 引入了对 WebAssembly 新版本的 异常处理提案 的支持。

此更新确保新提案符合 Kotlin 的要求,使得 Kotlin/Wasm 可以在仅支持提案最新版本的虚拟机上使用。

通过使用 -Xwasm-use-new-exception-proposal 编译器选项来激活新的异常处理提案。它默认是关闭的。

withWasm() 函数被拆分为 JS 和 WASI 变体

withWasm() 函数过去用于为层次结构模板提供 Wasm 目标,现在被专门化的 withWasmJs()withWasmWasi() 函数所取代。

现在,您可以在树定义中的不同组之间分离 WASI 和 JS 目标。

Kotlin/JS

除了其他更改之外,此版本还为 Kotlin 带来了现代 JS 编译,支持更多来自 ES2015 标准的功能:

新的编译目标

在 Kotlin 2.0.0 中,我们为 Kotlin/JS 添加了一个新的编译目标 es2015。这是您一次性启用 Kotlin 支持的所有 ES2015 功能的新方式。

您可以在 build.gradle(.kts) 文件中这样设置:

kotlin {
    js {
        compilerOptions {
            target.set("es2015")
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

新的目标自动开启 ES 类和模块 以及新支持的 ES 生成器

作为 ES2015 生成器的挂起函数

此版本引入了 实验性 支持,将 挂起函数 编译为 ES2015 生成器。

使用生成器而不是状态机应该会改善您项目的最终包大小。例如,JetBrains 团队通过使用 ES2015 生成器,将其 Space 项目的包大小减少了 20%。

在官方文档中了解更多关于 ES2015 (ECMAScript 2015, ES6) 的信息

向主函数传递参数

从 Kotlin 2.0.0 开始,您可以指定 main() 函数的 args 源。这个功能使得与命令行一起工作并传递参数变得更加容易。

为此,使用新的 passAsArgumentToMainFunction() 函数定义 js {} 块,该函数返回一个字符串数组:

kotlin {
    js {
        binary.executable()
        passAsArgumentToMainFunction("Deno.args")
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

该函数在运行时执行。它接受 JavaScript 表达式,并将其用作 main() 函数调用的 args: Array<String> 参数。

此外,如果您使用 Node.js 运行时,您可以利用一个特殊的别名。它允许您一次性将 process.argv 传递给 args 参数,而不是每次都手动添加:

kotlin {
    js {
        binary.executable()
        nodejs {
            passProcessArgvToMainFunction()
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Kotlin/JS 项目的按文件编译

Kotlin 2.0.0 引入了一个新的 Kotlin/JS 项目输出的粒度选项。现在,您可以设置按文件编译,为每个 Kotlin 文件生成一个 JavaScript 文件。这有助于显著优化最终包的大小并改善程序的加载时间。

以前,只有两种输出选项。Kotlin/JS 编译器可以为整个项目生成一个单独的 .js 文件。然而,这个文件可能太大且不便于使用。每当您想使用项目中的函数时,都必须将整个 JavaScript 文件作为依赖项包含。或者,您可以配置为每个项目模块编译一个单独的 .js 文件。这仍然是默认选项。

由于模块文件也可能太大,随着 Kotlin 2.0.0 的发布,我们添加了一个更细粒度的输出,为每个 Kotlin 文件生成一个(如果文件包含导出的声明,则为两个)JavaScript 文件。要启用按文件编译模式:

  1. 在您的构建文件中添加 useEsModules() 函数以支持 ECMAScript 模块:

    // build.gradle.kts
    kotlin {
        js(IR) {
            useEsModules() // 启用 ES2015 模块
            browser()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    您也可以使用新的 es2015 编译目标 来实现这一点。

  2. 应用 -Xir-per-file 编译器选项或更新您的 gradle.properties 文件:

    # gradle.properties
    kotlin.js.ir.output.granularity=per-file // `per-module` 是默认值
    
    • 1
    • 2

改进的集合互操作性

从 Kotlin 2.0.0 开始,可以将签名中带有 Kotlin 集合类型的声明导出到 JavaScript(和 TypeScript)。这适用于 SetMapList 集合类型及其可变对应类型。

要在 JavaScript 中使用 Kotlin 集合,首先使用 @JsExport 注解标记必要的声明:

// Kotlin
@JsExport
data class User(
    val name: String,
    val friends: List<User> = emptyList()
)

@JsExport
val me = User(
    name = "Me",
    friends = listOf(User(name = "Kodee"))
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

然后,您可以将它们作为常规 JavaScript 数组从 JavaScript 中消费:

// JavaScript
import { User, me, KtList } from "my-module"

const allMyFriendNames = me.friends
    .asJsReadonlyArrayView()
    .map(x => x.name) // [‘Kodee']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

不幸的是,从 JavaScript 创建 Kotlin 集合仍然不可用。我们计划在 Kotlin 2.0.20 中添加此功能。

{type=“note”}

支持 createInstance()

自 Kotlin 2.0.0 起,您可以在 Kotlin/JS 目标中使用来自 Kotlin 反射库的 createInstance() 函数。以前,它仅在 JVM 上可用。

这个来自 KClass 接口的函数创建指定类的新实例,这对于获取 Kotlin 类的运行时引用非常有用。

支持类型安全的纯 JavaScript 对象

js-plain-objects 插件是 实验性的。它可能会在任何时候被删除或更改。js-plain-objects 插件 支持 K2 编译器。

{type=“warning”}

为了更容易地使用 JavaScript API,在 Kotlin 2.0.0 中,我们提供了一个新插件:js-plain-objects,您可以使用它来创建类型安全的纯 JavaScript 对象。该插件检查您的代码中带有 @JsPlainObject 注解的任何 外部接口,并添加:

  • 在伴随对象中内联的 invoke 运算符函数,您可以用作构造函数。
  • 一个 .copy() 函数,您可以使用它来创建对象的副本,同时调整其某些属性。

例如:

import kotlinx.js.JsPlainObject

@JsPlainObject
external interface User {
    var name: String
    val age: Int
    val email: String?
}

fun main() {
    // 创建一个 JavaScript 对象
    val user = User(name = "Name", age = 10)
    // 复制对象并添加电子邮件
    val copy = user.copy(age = 11, email = "some@user.com")

    println(JSON.stringify(user))
    // { "name": "Name", "age": 10 }
    println(JSON.stringify(copy))
    // { "name": "Name", "age": 11, "email": "some@user.com" }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

使用这种方法创建的任何 JavaScript 对象都更安全,因为您不仅可以在运行时看到错误,还可以在编译时甚至在 IDE 中看到它们。

考虑这个示例,它使用 fetch() 函数与 JavaScript API 交互,使用外部接口来描述 JavaScript 对象的形状:

import kotlinx.js.JsPlainObject

@JsPlainObject
external interface FetchOptions {
    val body: String?
    val method: String
}

// 对 Window.fetch 的包装
suspend fun fetch(url: String, options: FetchOptions? = null) = TODO("在这里添加您的自定义行为")

// 触发编译时错误,因为 "metod" 未被识别为方法
fetch("https://google.com", options = FetchOptions(metod = "POST"))
// 触发编译时错误,因为方法是必需的
fetch("https://google.com", options = FetchOptions(body = "SOME STRING")) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

相比之下,如果您使用 js() 函数来创建 JavaScript 对象,错误只在运行时发现,或者根本没有触发:

suspend fun fetch(url: String, options: FetchOptions? = null) = TODO("在这里添加您的自定义行为")

// 没有触发错误。由于 "metod" 未被识别,使用了错误的方法(GET)。
fetch("https://google.com", options = js("{ metod: 'POST' }"))

// 默认使用 GET 方法。由于 body 不应该存在,触发运行时错误。
fetch("https://google.com", options = js("{ body: 'SOME STRING' }"))
// TypeError: Window.fetch: HEAD 或 GET 请求不能有 body
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

要使用 js-plain-objects 插件,请将以下内容添加到您的 build.gradle(.kts) 文件中:

plugins {
    kotlin("plugin.js-plain-objects") version "2.0.0"
}
  • 1
  • 2
  • 3
plugins {
    id "org.jetbrains.kotlin.plugin.js-plain-objects" version "2.0.0"
}
  • 1
  • 2
  • 3
### 支持 npm 包管理器

以前,Kotlin 多平台 Gradle 插件只能使用 Yarn 作为包管理器来下载和安装 npm 依赖项。从 Kotlin 2.0.0 开始,您可以使用 npm 作为您的包管理器。使用 npm 作为包管理器意味着在您的设置过程中需要管理的工具少了一个。

为了向后兼容,Yarn 仍然是默认的包管理器。要使用 npm 作为您的包管理器,请在您的 gradle.properties 文件中设置以下属性:

kotlin.js.yarn = false
  • 1

编译任务的更改

以前,webpackdistributeResources 编译任务都针对相同的目录。此外,distribution 任务也将 dist 作为其输出目录。这导致了输出重叠并产生了编译警告。

因此,从 Kotlin 2.0.0 开始,我们实施了以下更改:

  • webpack 任务现在针对一个单独的文件夹。
  • distributeResources 任务被完全删除。
  • distribution 任务现在具有 Copy 类型并针对 dist 文件夹。

停止支持遗留的 Kotlin/JS JAR 构件

从 Kotlin 2.0.0 开始,Kotlin 发行版不再包含带有 .jar 扩展名的遗留 Kotlin/JS 构件。遗留构件用于不受支持的旧 Kotlin/JS 编译器,对于使用 klib 格式的 IR 编译器是不必要的。

Gradle 改进

Kotlin 2.0.0 与 Gradle 6.8.3 至 8.5 完全兼容。您也可以使用最新的 Gradle 版本,但如果这样做,请记住您可能会遇到弃用警告,或者某些新的 Gradle 功能可能无法正常工作。

此版本带来了以下更改:

多平台项目中编译器选项的新 Gradle DSL

此功能是 实验性的。它可能会在任何时候被删除或更改。仅出于评估目的使用它。我们欢迎您在 YouTrack 上对此功能提供反馈。

{type=“warning”}

在 Kotlin 2.0.0 之前,使用 Gradle 在多平台项目中配置编译器选项只能在低级别进行,例如按任务、编译或源集。为了更容易地在您的项目中更广泛地配置编译器选项,Kotlin 2.0.0 带来了一个新的 Gradle DSL。

使用此新 DSL,您可以在扩展级别为所有目标和共享源集(如 commonMain)以及在目标级别为特定目标配置编译器选项:

kotlin {
    compilerOptions {
        // 扩展级别的通用编译器选项,用作所有目标和共享源集的默认值
        allWarningsAsErrors.set(true)
    }
    jvm {
        compilerOptions {
            // 目标级别的 JVM 编译器选项,用作此目标中所有编译的默认值
            noJdk.set(true)
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

现在,项目配置总体上有三个层次。最高级别是扩展级别,然后是目标级别,最低级别是编译单元(通常是编译任务):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传{width=700}

较高级别的设置用作较低级别的约定(默认值):

  • 扩展编译器选项的值是目标编译器选项的默认值,包括共享源集,如 commonMainnativeMaincommonTest
  • 目标编译器选项的值用作编译单元(任务)编译器选项的默认值,例如 compileKotlinJvmcompileTestKotlinJvm 任务。

反过来,较低级别的配置会覆盖较高级别的相关设置:

  • 任务级别的编译器选项覆盖目标或扩展级别的相关配置。
  • 目标级别的编译器选项覆盖扩展级别的相关配置。

在配置您的项目时,请记住,一些设置编译器选项的旧方法已被 弃用

我们鼓励您在您的多平台项目中尝试新的 DSL,并在 YouTrack 上留下反馈,因为我们计划将此 DSL 作为配置编译器选项的推荐方法。

新的 Compose 编译器 Gradle 插件

Jetpack Compose 编译器,它将组合项翻译成 Kotlin 代码,现在已经合并到 Kotlin 仓库中。这将有助于将 Compose 项目过渡到 Kotlin 2.0.0,因为 Compose 编译器将始终与 Kotlin 同时发布。这也将 Compose 编译器版本提升到 2.0.0。

要在您的项目中使用新的 Compose 编译器,请在您的 build.gradle(.kts) 文件中应用 org.jetbrains.kotlin.plugin.compose Gradle 插件,并将其版本设置为 Kotlin 2.0.0。

要了解更多关于此更改的信息并查看迁移说明,请参阅 Compose 编译器 文档。

区分 JVM 和 Android 发布库的新属性

从 Kotlin 2.0.0 开始,默认情况下,所有 Kotlin 变体都会发布 org.gradle.jvm.environment Gradle 属性。

该属性有助于区分 Kotlin 多平台库的 JVM 和 Android 变体。它表明某个库变体更适合特定的 JVM 环境。目标环境可以是 “android”、“standard-jvm” 或 “no-jvm”。

发布此属性应该使消费具有 JVM 和 Android 目标的 Kotlin 多平台库对于非多平台客户端(如仅 Java 项目)更加健壮。

如果需要,您可以禁用属性发布。为此,请将以下 Gradle 选项添加到您的 gradle.properties 文件中:

kotlin.publishJvmEnvironmentAttribute=false
  • 1

Kotlin/Native 中 CInteropProcess 的 Gradle 依赖处理改进

在此版本中,我们增强了 defFile 属性的处理,以确保 Kotlin/Native 项目中更好的 Gradle 任务依赖管理。

在此更新之前,如果将 defFile 属性指定为尚未执行的另一个任务的输出,则 Gradle 构建可能会失败。解决此问题的方法是添加对此任务的依赖:

kotlin {
    macosArm64("native") {
        compilations.getByName("main") {
            cinterops {
                val cinterop by creating {
                    defFileProperty.set(createDefFileTask.flatMap { it.defFile.asFile })
                    project.tasks.named(interopProcessingTaskName).configure {
                        dependsOn(createDefFileTask)
                    }
                }
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

为了解决这个问题,我们添加了一个名为 definitionFileRegularFileProperty 属性。现在,Gradle 在构建过程后期连接任务运行后,懒加载验证 definitionFile 属性的存在。这种新方法消除了对额外依赖的需求。

CInteropProcess 任务和 CInteropSettings 类使用 definitionFile 属性而不是 defFiledefFileProperty

kotlin {
    macosArm64("native") {
        compilations.getByName("main") {
            cinterops {
                val cinterop by creating {
                    definitionFile.set(project.file("def-file.def"))
                }
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
kotlin {
    macosArm64("native") {
        compilations.main {
            cinterops {
                cinterop {
                    definitionFile.set(project.file("def-file.def"))
                }
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

defFiledefFileProperty 参数已弃用。

{type=“warning”}

Gradle 中的可见性更改

此更改仅影响 Kotlin DSL 用户。

{type=“note”}

在 Kotlin 2.0.0 中,我们对 Kotlin Gradle 插件进行了修改,以便在您的构建脚本中更好地控制和安全性。以前,某些 Kotlin DSL 函数和属性旨在用于特定的 DSL 上下文,会无意中泄漏到其他 DSL 上下文中。这种泄漏可能导致使用错误的编译器选项、设置被多次应用以及其他配置错误:

kotlin {
    // 目标 DSL 无法访问在 kotlin{} 扩展 DSL 中定义的方法和属性
    jvm {
        // 编译 DSL 无法访问在 kotlin{} 扩展 DSL 和 Kotlin jvm{} 目标 DSL 中定义的方法和属性
        compilations.configureEach {
            // 编译任务 DSL 无法访问在 kotlin{} 扩展、Kotlin jvm{} 目标或 Kotlin 编译 DSL 中定义的方法和属性
            compileTaskProvider.configure {
                // 例如:
                explicitApi()
                // 错误,因为它是在 kotlin{} 扩展 DSL 中定义的
                mavenPublication {}
                // 错误,因为它是在 Kotlin jvm{} 目标 DSL 中定义的
                defaultSourceSet {}
                // 错误,因为它是在 Kotlin 编译 DSL 中定义的
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

为了解决这个问题,我们添加了 @KotlinGradlePluginDsl 注解,防止 Kotlin Gradle 插件 DSL 函数和属性暴露到它们不应该可用的级别。以下级别彼此分离:

  • Kotlin 扩展
  • Kotlin 目标
  • Kotlin 编译
  • Kotlin 编译任务

对于最常见的情况,如果您的构建脚本配置不正确,我们添加了带有修复建议的编译器警告。例如:

kotlin {
    jvm {
        sourceSets.getByName("jvmMain").dependencies {
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3")
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这种情况下,sourceSets 的警告消息是:

[DEPRECATION] 'sourceSets: NamedDomainObjectContainer<KotlinSourceSet>' is deprecated.Accessing 'sourceSets' container on the Kotlin target level DSL is deprecated. Consider configuring 'sourceSets' on the Kotlin extension level.
  • 1

我们欢迎您对这一变化提供反馈!直接在 #gradle Slack 频道 上与 Kotlin 开发人员分享您的评论。获取 Slack 邀请

Gradle 项目中 Kotlin 数据的新目录

使用此更改,您可能需要将 .kotlin 目录添加到项目的 .gitignore 文件中。

{type=“warning”}

在 Kotlin 1.8.20 中,Kotlin Gradle 插件切换到将其数据存储在 Gradle 项目缓存目录中:<project-root-directory>/.gradle/kotlin。然而,.gradle 目录是为 Gradle 保留的,因此它不是面向未来的。

为了解决这个问题,从 Kotlin 2.0.0 开始,我们将默认在 <project-root-directory>/.kotlin 中存储 Kotlin 数据。为了向后兼容,我们仍将在 .gradle/kotlin 目录中存储一些数据。

您可以配置的新 Gradle 属性是:

Gradle 属性描述
kotlin.project.persistent.dir配置存储项目级数据的位置。默认值:<project-root-directory>/.kotlin
kotlin.project.persistent.dir.gradle.disableWrite一个布尔值,控制是否禁用将 Kotlin 数据写入 .gradle 目录。默认值:false

要将这些属性添加到您的项目中以使其生效,请将它们添加到 gradle.properties 文件中。

按需下载 Kotlin/Native 编译器

在 Kotlin 2.0.0 之前,如果您在多平台项目的 Gradle 构建脚本中配置了 Kotlin/Native 目标,Gradle 总是会在 配置阶段 下载 Kotlin/Native 编译器。

即使没有任务在 执行阶段 运行,也会发生这种情况,该任务需要为 Kotlin/Native 目标编译代码。以这种方式下载 Kotlin/Native 编译器对于只想检查其项目中的 JVM 或 JavaScript 代码的用户来说特别低效。例如,在 CI 过程中将其 Kotlin 项目作为测试或检查的一部分。

在 Kotlin 2.0.0 中,我们更改了 Kotlin Gradle 插件中的此行为,以便在 执行阶段 下载 Kotlin/Native 编译器,并且 当请求为 Kotlin/Native 目标进行编译时。

反过来,Kotlin/Native 编译器的依赖项现在不是作为编译器的一部分下载,而是在执行阶段下载。

如果您遇到新行为的任何问题,可以通过在 gradle.properties 文件中添加以下 Gradle 属性暂时切换回以前的行为:

kotlin.native.toolchain.enabled=false
  • 1

从版本 1.9.20-Beta 开始,Kotlin/Native 分发已发布到 Maven Central 以及 CDN。

这使我们能够更改 Kotlin 查找和下载必要工件的方式。它现在默认使用您在项目中的 repositories {} 块中指定的 Maven 存储库,而不是 CDN。

您可以通过在 gradle.properties 文件中设置以下 Gradle 属性暂时切换回此行为:

kotlin.native.distribution.downloadFromMaven=false.
  • 1

请在我们的问题跟踪器 YouTrack 上报告任何问题。这两个更改默认行为的 Gradle 属性都是临时的,将在未来的版本中删除。

弃用定义编译器选项的旧方法

在此版本中,我们继续改进您可以设置编译器选项的方式。它应该解决不同方式之间的歧义,并使项目配置更直接。

自 Kotlin 2.0.0 起,以下指定编译器选项的 DSL 已弃用:

  • 实现所有 Kotlin 编译任务的 KotlinCompile 接口中的 kotlinOptions DSL。请改用 KotlinCompilationTask<CompilerOptions>

  • KotlinCompiation 接口中具有 HasCompilerOptions 类型的 compilerOptions 属性。此 DSL 与其他 DSL 不一致,并且配置了与 KotlinCompilation.compileTaskProvider 编译任务中的 compilerOptions 相同的 KotlinCommonCompilerOptions 对象,这令人困惑。

    相反,我们建议使用 Kotlin 编译任务中的 compilerOptions 属性:

    kotlinCompilation.compileTaskProvider.configure {
        compilerOptions { ... }
    }
    
    • 1
    • 2
    • 3

    例如:

    kotlin {
        js(IR) {
            compilations.all {
                compileTaskProvider.configure {
                    compilerOptions.freeCompilerArgs.add("-Xerror-tolerance-policy=SYNTAX")
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • KotlinCompilation 接口中的 kotlinOptions DSL。

  • KotlinNativeArtifactConfig 接口、KotlinNativeLink 类和 KotlinNativeLinkArtifactTask 类中的 kotlinOptions DSL。请改用 toolOptions DSL。

  • KotlinJsDce 接口中的 dceOptions DSL。请改用 toolOptions DSL。

有关如何在 Kotlin Gradle 插件中指定编译器选项的更多信息,请参阅 如何定义选项

提高最低支持 AGP 版本

从 Kotlin 2.0.0 开始,最低支持的 Android Gradle 插件版本为 7.1.3。

尝试最新语言版本的新 Gradle 属性

在 Kotlin 2.0.0 之前,我们有一个 Gradle 属性来尝试新的 K2 编译器:kotlin.experimental.tryK2。现在 K2 编译器在 Kotlin 2.0.0 中默认启用,我们决定将此属性演变为一种新形式,您可以在项目中使用它来尝试最新的语言版本:kotlin.experimental.tryNext。当您在 gradle.properties 文件中使用此属性时,Kotlin Gradle 插件会将语言版本增加到您的 Kotlin 版本默认值之上。例如,在 Kotlin 2.0.0 中,默认语言版本为 2.0,因此该属性配置语言版本 2.1。

此新 Gradle 属性在 构建报告 中产生与以前使用 kotlin.experimental.tryK2 类似的指标。配置的语言版本包含在输出中。例如:

##### 'kotlin.experimental.tryNext' results #####
:app:compileKotlin: 2.1 language version
:lib:compileKotlin: 2.1 language version
##### 100% (2/2) tasks have been compiled with Kotlin 2.1 #####
  • 1
  • 2
  • 3
  • 4

要了解如何启用构建报告及其内容,请参阅 构建报告

构建报告的新 JSON 输出格式

在 Kotlin 1.7.0 中,我们引入了构建报告以帮助跟踪编译器性能。随着时间的推移,我们添加了更多指标,以使这些报告在调查性能问题时更加详细和有用。以前,本地文件的唯一输出格式是 *.txt 格式。在 Kotlin 2.0.0 中,我们支持 JSON 输出格式,以便使用其他工具进行分析更加容易。

要在构建报告中配置 JSON 输出格式,请在 gradle.properties 文件中声明以下属性:

kotlin.build.report.output=json

// 存储构建报告的目录
kotlin.build.report.json.directory="my/directory/path"
  • 1
  • 2
  • 3
  • 4

或者,您可以运行以下命令:

./gradlew assemble -Pkotlin.build.report.output=json -Pkotlin.build.report.json.directory="my/directory/path"
  • 1

配置后,Gradle 会在您指定的目录中生成构建报告,名称为:${project_name}-date-time-<sequence_number>.json

以下是包含构建指标和聚合指标的构建报告 JSON 输出格式示例片段:

"buildOperationRecord": [
    {
     "path": ":lib:compileKotlin",
      "classFqName": "org.jetbrains.kotlin.gradle.tasks.KotlinCompile_Decorated",
      "startTimeMs": 1714730820601,
      "totalTimeMs": 2724,
      "buildMetrics": {
        "buildTimes": {
          "buildTimesNs": {
            "CLEAR_OUTPUT": 713417,
            "SHRINK_AND_SAVE_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION": 19699333,
            "IR_TRANSLATION": 281000000,
            "NON_INCREMENTAL_LOAD_CURRENT_CLASSPATH_SNAPSHOT": 14088042,
            "CALCULATE_OUTPUT_SIZE": 1301500,
            "GRADLE_TASK": 2724000000,
            "COMPILER_INITIALIZATION": 263000000,
            "IR_GENERATION": 74000000,}
        }"aggregatedMetrics": {
    "buildTimes": {
      "buildTimesNs": {
        "CLEAR_OUTPUT": 782667,
        "SHRINK_AND_SAVE_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION": 22031833,
        "IR_TRANSLATION": 333000000,
        "NON_INCREMENTAL_LOAD_CURRENT_CLASSPATH_SNAPSHOT": 14890292,
        "CALCULATE_OUTPUT_SIZE": 2370750,
        "GRADLE_TASK": 3234000000,
        "COMPILER_INITIALIZATION": 292000000,
        "IR_GENERATION": 89000000,}
    }
  • 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

kapt 配置从父配置继承注解处理器

在 Kotlin 2.0.0 之前,如果您想在单独的 Gradle 配置中定义一组公共注解处理器,并在子项目的 kapt 特定配置中扩展此配置,kapt 会跳过注解处理,因为它找不到任何注解处理器。在 Kotlin 2.0.0 中,kapt 可以成功检测到您的注解处理器存在间接依赖。

例如,对于使用 Dagger 的子项目,在您的 build.gradle(.kts) 文件中,使用以下配置:

val commonAnnotationProcessors by configurations.creating
configurations.named("kapt") { extendsFrom(commonAnnotationProcessors) }

dependencies {
    implementation("com.google.dagger:dagger:2.48.1")
    commonAnnotationProcessors("com.google.dagger:dagger-compiler:2.48.1")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在此示例中,commonAnnotationProcessors Gradle 配置是您希望用于所有项目的注解处理的“公共”配置。您使用 extendsFrom() 方法将 commonAnnotationProcessors 添加为父配置。kapt 看到 commonAnnotationProcessors Gradle 配置依赖于 Dagger 注解处理器,并成功将其包含在其注解处理的配置中。

感谢 Christoph Loy 实现了 此功能

Kotlin Gradle 插件不再使用已弃用的 Gradle 约定

在 Kotlin 2.0.0 之前,如果您使用 Gradle 8.2 或更高版本,Kotlin Gradle 插件错误地使用了 Gradle 8.2 中已弃用的约定。这导致 Gradle 报告构建弃用。在 Kotlin 2.0.0 中,Kotlin Gradle 插件已更新,以便在使用 Gradle 8.2 或更高版本时不再触发这些弃用警告。

标准库

此版本为 Kotlin 标准库带来了进一步的稳定性,并使更多现有函数在所有平台上通用:

枚举类 values 泛型函数的稳定替代品

在 Kotlin 2.0.0 中,enumEntries<T>() 函数成为 稳定enumEntries<T>() 函数是泛型 enumValues<T>() 函数的替代品。新函数返回给定枚举类型 T 的所有枚举条目的列表。之前引入的枚举类的 entries 属性也已稳定,以替换合成 values() 函数。有关 entries 属性的更多信息,请参阅 Kotlin 1.8.20 的新特性

enumValues<T>() 函数仍然受支持,但我们建议您使用 enumEntries<T>() 函数,因为它对性能的影响较小。每次调用 enumValues<T>() 时,都会创建一个新数组,而每次调用 enumEntries<T>() 时,都会返回同一个列表,这要高效得多。

{type=“tip”}

例如:

enum class RGB { RED, GREEN, BLUE }

inline fun <reified T : Enum<T>> printAllValues() {
    print(enumEntries<T>().joinToString { it.name })
}

printAllValues<RGB>()
// RED, GREEN, BLUE
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

稳定的 AutoCloseable 接口

在 Kotlin 2.0.0 中,公共 AutoCloseable 接口成为 稳定。它允许您轻松关闭资源,并包含一些有用的函数:

  • use() 扩展函数,它对选定的资源执行给定的块函数,然后正确关闭它,无论是否抛出异常。
  • AutoCloseable() 构造函数函数,它创建 AutoCloseable 接口的实例。

在下面的示例中,我们定义了 XMLWriter 接口,并假设有一个实现它的资源。例如,此资源可能是一个类,它打开一个文件,写入 XML 内容,然后关闭它:

interface XMLWriter {
    fun document(encoding: String, version: String, content: XMLWriter.() -> Unit)
    fun element(name: String, content: XMLWriter.() -> Unit)
    fun attribute(name: String, value: String)
    fun text(value: String)

    fun flushAndClose()
}

fun writeBooksTo(writer: XMLWriter) {
    val autoCloseable = AutoCloseable { writer.flushAndClose() }
    autoCloseable.use {
        writer.document(encoding = "UTF-8", version = "1.0") {
            element("bookstore") {
                element("book") {
                    attribute("category", "fiction")
                    element("title") { text("Harry Potter and the Prisoner of Azkaban") }
                    element("author") { text("J. K. Rowling") }
                    element("year") { text("1999") }
                    element("price") { text("29.99") }
                }
                element("book") {
                    attribute("category", "programming")
                    element("title") { text("Kotlin in Action") }
                    element("author") { text("Dmitry Jemerov") }
                    element("author") { text("Svetlana Isakova") }
                    element("year") { text("2017") }
                    element("price") { text("25.19") }
                }
            }
        }
    }
}
  • 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

AbstractMutableList.modCount 的公共受保护属性

在此版本中,AbstractMutableList 接口的 modCount protected 属性成为公共属性。以前,modCount 属性在每个平台上都可用,但对于公共目标则不可用。现在,您可以在公共代码中创建 AbstractMutableList 的自定义实现并访问该属性。

该属性跟踪对集合所做的结构修改的数量。这包括更改集合大小或以可能导致正在进行迭代返回错误结果的方式更改列表的操作。

您可以使用 modCount 属性来注册和检测实现自定义列表时的并发修改。

AbstractMutableList.removeRange 的公共受保护函数

在此版本中,AbstractMutableList 接口的 removeRange() protected 函数成为公共函数。以前,它在每个平台上都可用,但对于公共目标则不可用。现在,您可以在公共代码中创建 AbstractMutableList 的自定义实现并覆盖该函数。

该函数从该列表中删除指定范围内的元素。通过覆盖此函数,您可以利用自定义实现并提高列表操作的性能。

String.toCharArray(destination) 的公共函数

此版本引入了一个公共 String.toCharArray(destination) 函数。以前,它仅在 JVM 上可用。

让我们将其与现有的 String.toCharArray() 函数进行比较。它创建一个包含指定字符串中字符的新 CharArray。然而,新的公共 String.toCharArray(destination) 函数将 String 字符移动到现有的目标 CharArray 中。如果您已经有一个想要填充的缓冲区,这将非常有用:

fun main() {
    val myString = "Kotlin is awesome!"
    val destinationArray = CharArray(myString.length)

    // 将字符串转换并存储在 destinationArray 中:
    myString.toCharArray(destinationArray)

    for (char in destinationArray) {
        print("$char ")
        // K o t l i n   i s   a w e s o m e ! 
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

{kotlin-runnable=“true”}

安装 Kotlin 2.0.0

从 IntelliJ IDEA 2023.3 和 Android Studio Iguana (2023.2.1) Canary 15 开始,Kotlin 插件作为捆绑插件包含在您的 IDE 中。这意味着您无法再从 JetBrains Marketplace 安装插件。

要更新到新的 Kotlin 版本,请在构建脚本中 更改 Kotlin 版本 为 2.0.0。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/747542
推荐阅读
相关标签
  

闽ICP备14008679号