13.6.7.1 Kotlin 1.7.20 的新变化

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

13.6.7.1 Kotlin 1.7.20 的新变化

阅读 Kotlin 1.7.20 的发行说明,涵盖新的语言特性、Kotlin Multiplatform、JVM、Native、JS 的更新,以及对 Gradle 和 Maven 构建工具的支持。

Kotlin 1.7.20 的 IDE 支持适用于 IntelliJ IDEA 2021.3、2022.1 和 2022.2。

发布日期:2022 年 9 月 29 日

Kotlin 1.7.20 发布了!以下是本次发布的一些亮点:

你也可以在这个视频中查看变更简介:

视频:What’s new in Kotlin 1.7.20

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

支持 Kotlin K2 编译器插件

Kotlin 团队正在继续稳定 K2 编译器。K2 仍处于 Alpha 阶段(如 Kotlin 1.7.0 版本中宣布的那样),但它现在支持若干编译器插件。你可以关注这个 YouTrack 议题来获取 Kotlin 团队关于新编译器的更新。

从 1.7.20 版本开始,Kotlin K2 编译器支持以下插件:

警告: 新版 K2 编译器的 Alpha 版本仅适用于 JVM 项目。它不支持 Kotlin/JS、Kotlin/Native 或其他多平台项目。

在以下视频中进一步了解新编译器及其优势:

如何启用 Kotlin K2 编译器

要启用并测试 Kotlin K2 编译器,请使用以下编译器选项:

1
-Xuse-k2

你可以在 build.gradle(.kts) 文件中指定它:

Kotlin

1
2
3
tasks.withType<KotlinCompile> {
    kotlinOptions.useK2 = true
}

Groovy

1
2
3
compileKotlin {
    kotlinOptions.useK2 = true
}

在你的 JVM 项目上体验性能提升,并与旧编译器的结果进行比较。

关于新 K2 编译器留下你的反馈

我们非常欢迎你以任何形式提供反馈:

语言

Kotlin 1.7.20 引入了新语言特性的预览版本,并对构建器类型推断施加了限制:

用于创建开放式区间的 ..< 运算符预览

警告: 新运算符是实验性的,并且它在 IDE 中的支持有限。

本次发布引入了新的 ..< 运算符。Kotlin 有 .. 运算符用于表示值区间。新的 ..< 运算符的作用类似 until 函数,帮助你定义开放式区间。

视频:New operator for open-ended ranges

我们的研究表明,这个新运算符能更好地表达开放式区间,并清楚地表明上界不包含在内。

下面是在 when 表达式中使用 ..< 运算符的例子:

1
2
3
4
5
6
when (value) {
    in 0.0..<0.25 -> // 第一季度
    in 0.25..<0.5 -> // 第二季度
    in 0.5..<0.75 -> // 第三季度
    in 0.75..1.0 -> // 最后一个季度  <- 注意这里是闭区间
}

标准库 API 变化

以下新类型和操作将在公共 Kotlin 标准库的 kotlin.ranges 包中引入:

新的 OpenEndRange 接口

用于表示开放式区间的新接口与现有的 ClosedRange<T> 接口非常相似:

1
2
3
4
5
6
7
8
interface OpenEndRange<T : Comparable<T>> {
    // 下界
    val start: T
    // 上界,不包含在区间内
    val endExclusive: T
    operator fun contains(value: T): Boolean = value >= start && value < endExclusive
    fun isEmpty(): Boolean = start >= endExclusive
}
在现有可迭代区间中实现 OpenEndRange

当开发者需要获得上界被排除的区间时,目前会使用 until 函数,实际上生成一个具有相同值的闭区间可迭代范围。为了让这些区间能被接受 OpenEndRange<T> 的新 API 接受,我们希望在现有的可迭代范围中实现该接口:IntRange、LongRange、CharRange、UIntRange 和 ULongRange。这样它们将同时实现 ClosedRange<T> 和 OpenEndRange<T> 接口。

1
2
3
4
5
class IntRange : IntProgression(...), ClosedRange<Int>, OpenEndRange<Int> {
    override val start: Int
    override val endInclusive: Int
    override val endExclusive: Int
}
标准类型的 rangeUntil 运算符

rangeUntil 运算符将针对目前由 rangeTo 运算符定义的相同类型和组合提供。作为原型,我们以扩展函数的形式提供它们,但为了一致性,我们计划在稳定开放式区间 API 之前把它们改为成员。

如何启用 ..< 运算符

要使用 ..< 运算符,或为你自己的类型实现该运算符约定,请启用 -language-version 1.8 编译器选项。

为支持标准类型的开放式区间而引入的新 API 元素需要选择启用,这与实验性标准库 API 的惯例一致:@OptIn(ExperimentalStdlibApi::class)。或者,你也可以使用 -opt-in=kotlin.ExperimentalStdlibApi 编译器选项。

在这个 KEEP 文档中进一步了解新运算符。

用 data object 改进单例和密封类层次结构的字符串表示

警告: data object 是实验性的,目前 IDE 中的支持有限。

本次发布引入了一种新的 object 声明类型供你使用:data object。Data object 在概念上与普通的 object 声明行为相同,但开箱即用地带有简洁的 toString 表示。

视频:Data objects in Kotlin 1.7.20

1
2
3
4
5
6
7
8
package org.example
object MyObject
data object MyDataObject

fun main() {
    println(MyObject) // org.example.MyObject@1f32e575
    println(MyDataObject) // MyDataObject
}

这使 data object 声明非常适合密封类层次结构,你可以在其中把它们与 data class 声明一起使用。在这段代码中,把 EndOfFile 声明为 data object 而不是普通 object,意味着它会获得漂亮的 toString,无需手动覆盖,从而与相伴的 data class 定义保持对称:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
sealed class ReadResult {
    data class Number(val value: Int) : ReadResult()
    data class Text(val value: String) : ReadResult()
    data object EndOfFile : ReadResult()
}

fun main() {
    println(ReadResult.Number(1)) // Number(value=1)
    println(ReadResult.Text("Foo")) // Text(value=Foo)
    println(ReadResult.EndOfFile) // EndOfFile
}

如何启用 data object

要在代码中使用 data object 声明,请启用 -language-version 1.9 编译器选项。在 Gradle 项目中,你可以通过在 build.gradle(.kts) 中添加以下内容来做到:

Kotlin

1
2
3
4
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
    // ...
    kotlinOptions.languageVersion = "1.9"
}

Groovy

1
2
3
4
compileKotlin {
    // ...
    kotlinOptions.languageVersion = '1.9'
}

在相应的 KEEP 文档中进一步了解 data object,并分享你对其实现的反馈。

新的构建器类型推断限制

Kotlin 1.7.20 对使用构建器类型推断施加了若干重大限制,这可能影响你的代码。这些限制适用于包含构建器 lambda 函数的代码,其中不分析 lambda 本身就无法推导出该参数。该参数会被用作实参。现在,编译器会始终为这类代码报错,并要求你显式指定类型。

这是一个破坏性变更,但我们的研究表明这些情况非常罕见,这些限制不应影响你的代码。如果确实影响了,请考虑以下情况:

  • 使用隐藏成员的扩展进行构建器推断。

如果你的代码包含一个同名扩展函数,且它会在构建器推断期间被使用,编译器会报错:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    class Data {
        fun doSmth() {} // 1
    }

    fun <T> T.doSmth() {} // 2

    fun test() {
        buildList {
            this.add(Data())
            this.get(0).doSmth() // 解析为 2,导致错误
        }
    }

要修复代码,你应显式指定类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    class Data {
        fun doSmth() {} // 1
    }

    fun <T> T.doSmth() {} // 2

    fun test() {
        buildList<Data> { // 类型实参!
            this.add(Data())
            this.get(0).doSmth() // 解析为 1
        }
    }
  • 有多个 lambda 且未显式指定类型实参时的构建器推断。

如果构建器推断中有两个或更多 lambda 块,它们会影响类型。为避免报错,编译器要求你指定类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
    fun <T: Any> buildList(
        first: MutableList<T>.() -> Unit,
        second: MutableList<T>.() -> Unit
    ): List<T> {
        val list = mutableListOf<T>()
        list.first()
        list.second()
        return list
    }

    fun main() {
        buildList(
            first = { // this: MutableList<String>
                add("")
            },
            second = { // this: MutableList<Int>
                val i: Int = get(0)
                println(i)
            }
        )
    }

要修复该错误,你应显式指定类型并修正类型不匹配:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    fun main() {
        buildList<Int>(
            first = { // this: MutableList<Int>
                add(0)
            },
            second = { // this: MutableList<Int>
                val i: Int = get(0)
                println(i)
            }
        )
    }

如果你没有找到上面提到的你的情况,请向我们的团队提交 issue。

关于这次构建器推断更新的更多信息,请参阅这个 YouTrack 议题。

Kotlin/JVM

Kotlin 1.7.20 引入了泛型内联类,为委托属性添加了更多字节码优化,并在 kapt 桩生成任务中支持 IR,从而使 kapt 可以使用所有最新的 Kotlin 特性:

泛型内联类

警告: 泛型内联类是实验性特性。它可能随时被移除或更改。需要选择启用(详见下文),并且你应仅将其用于评估目的。我们非常欢迎你在 YouTrack 上提供反馈。

Kotlin 1.7.20 允许 JVM 内联类的底层类型是一个类型参数。编译器会把它映射为 Any?,或者更一般地说,映射为该类型参数的上界。

视频:Generic inline classes in Kotlin 1.7.20

考虑以下示例:

1
2
3
4
@JvmInline
value class UserId<T>(val value: T)

fun compute(s: UserId<String>) {} // 编译器生成 fun compute-<hashcode>(s: Any?)

该函数接受该内联类作为参数。该参数被映射为上界,而不是类型实参。

要启用该特性,请使用 -language-version 1.8 编译器选项。

我们非常欢迎你在 YouTrack 上提供关于该特性的反馈。

更多委托属性的优化场景

在 Kotlin 1.6.0 中,我们通过省略 $delegate 字段并生成对所引用属性的直接访问优化了委托给某个属性的场景。在 1.7.20 中,我们为更多场景实现了这一优化。现在,如果委托对象是以下情况,$delegate 字段将被省略:

  • 具名对象:
1
2
3
4
5
  object NamedObject {
      operator fun getValue(thisRef: Any?, property: KProperty<*>): String = ...
  }

  val s: String by NamedObject
  • 同一模块中带幕后字段和默认 getter 的 final val 属性:
1
2
3
4
5
  val impl: ReadOnlyProperty<Any?, String> = ...

  class A {
      val s: String by impl
  }
  • 常量表达式、枚举条目、this 或 null。下面是 this 的示例:
1
2
3
4
5
  class A {
      operator fun getValue(thisRef: Any?, property: KProperty<*>) ...

      val s by this
  }

进一步了解委托属性。

我们非常欢迎你在 YouTrack 上提供关于该特性的反馈。

kapt 桩生成任务支持 JVM IR 后端

警告: kapt 桩生成任务中对 JVM IR 后端的支持是实验性特性。它可能随时发生变化。需要选择启用(详见下文),并且你应仅将其用于评估目的。

在 1.7.20 之前,kapt 桩生成任务使用旧后端,可重复注解无法与 kapt 一起使用。在 Kotlin 1.7.20 中,我们为 kapt 桩生成任务添加了对 JVM IR 后端的支持。这使得 kapt 可以使用所有最新的 Kotlin 特性,包括可重复注解。

要在 kapt 中使用 IR 后端,请在 gradle.properties 文件中添加以下选项:

kapt.use.jvm.ir=true

我们非常欢迎你在 YouTrack 上提供关于该特性的反馈。

Kotlin/Native

Kotlin 1.7.20 默认启用了新的 Kotlin/Native 内存管理器,并让你可以自定义 Info.plist 文件:

默认启用新的 Kotlin/Native 内存管理器

本次发布为新内存管理器带来了进一步的稳定性和性能改进,使我们能够把新内存管理器提升为 Beta。

旧的内存管理器使编写并发和异步代码变得复杂,包括实现 kotlinx.coroutines 库时遇到的问题。这阻碍了 Kotlin Multiplatform Mobile 的采用,因为并发限制在 iOS 与 Android 平台之间共享 Kotlin 代码时造成了麻烦。新内存管理器终于为把 Kotlin Multiplatform Mobile 提升为 Beta 铺平了道路。

新内存管理器还支持编译器缓存,使编译时间与此前版本相当。关于新内存管理器优势的更多内容,请参阅我们为预览版发布的原始博客文章。你可以在文档中找到更多技术细节。

配置与设置

从 Kotlin 1.7.20 开始,新内存管理器成为默认选项。不需要太多额外设置。

如果你此前已手动开启它,可以从 gradle.properties 中移除 kotlin.native.binary.memoryModel=experimental 选项,或从 build.gradle(.kts) 文件中移除 binaryOptions["memoryModel"] = "experimental"。

如有必要,你可以通过在 gradle.properties 中设置 kotlin.native.binary.memoryModel=strict 选项切回旧的内存管理器。不过,旧内存管理器不再支持编译器缓存,因此编译时间可能会变差。

冻结

在新内存管理器中,冻结已被弃用。除非你的代码需要与旧内存管理器(其中冻结仍是必需的)配合工作,否则不要使用它。这对需要继续支持旧内存管理器的库作者,或希望在新内存管理器出现问题时留有余地的开发者可能有帮助。

在这些情况下,你可以临时同时支持新内存管理器和旧内存管理器的代码。要忽略弃用警告,请执行以下操作之一:

  • 用 @OptIn(FreezingIsDeprecated::class) 注解对已弃用 API 的使用。
  • 在 Gradle 中对所有 Kotlin 源集应用 languageSettings.optIn("kotlin.native.FreezingIsDeprecated")。
  • 传入编译器标志 -opt-in=kotlin.native.FreezingIsDeprecated。

从 Swift/Objective-C 调用 Kotlin 挂起函数

新内存管理器仍然限制从主线程之外调用 Swift 和 Objective-C 中的 Kotlin suspend 函数,但你可以通过一个新的 Gradle 选项解除该限制。

该限制最初是在旧内存管理器中引入的,因为有些代码会把续体调度到原始线程上恢复。如果该线程没有受支持的事件循环,任务就永远不会运行,协程也永远不会恢复。

在某些情况下该限制已不再必要,但很难实现对所有必要条件进行检查。因此,我们决定在新内存管理器中保留该限制,同时引入一个选项让你可以禁用它。为此,请在 gradle.properties 中添加以下选项:

kotlin.native.binary.objcExportSuspendFunctionLaunchThreadRestriction=none

警告: 如果你使用 native-mt 版本的 kotlinx.coroutines 或其他采用相同“调度到原始线程”方式的库,请不要添加该选项。

Kotlin 团队非常感谢 Ahmed El-Helw 实现了该选项。

留下你的反馈

这是对我们生态系统的一次重大变更。我们非常欢迎你的反馈,帮助把它做得更好。

在你的项目上试用新内存管理器,并在我们的问题跟踪器 YouTrack 中分享反馈。

自定义 Info.plist 文件

在生成 framework 时,Kotlin/Native 编译器会生成信息属性列表文件 Info.plist。此前定制其内容很麻烦。在 Kotlin 1.7.20 中,你可以直接设置以下属性:

| 属性 | 二进制选项 |

| CFBundleIdentifier | bundleId | | CFBundleShortVersionString | bundleShortVersionString | | CFBundleVersion | bundleVersion |

为此,请使用相应的二进制选项。传入 -Xbinary=$option=$value 编译器标志,或为所需的 framework 设置 binaryOption(option, value) Gradle DSL。

Kotlin 团队非常感谢 Mads Ager 实现了该特性。

Kotlin/JS

Kotlin/JS 获得了一些改进开发者体验和提升性能的增强:

  • 得益于依赖加载效率的改进,klib 生成在增量构建和干净构建中都更快了。
  • 开发版二进制文件的增量编译已重做,在干净构建场景中带来重大改进、更快的增量构建以及稳定性修复。
  • 我们改进了针对嵌套对象、密封类以及构造器中带默认值参数的 .d.ts 生成。

Gradle

Kotlin Gradle 插件的更新侧重于与新 Gradle 特性及最新 Gradle 版本的兼容性。

Kotlin 1.7.20 包含支持 Gradle 7.1 的变更。已弃用的方法和属性被移除或替换,从而减少了 Kotlin Gradle 插件产生的弃用警告数量,并为将来支持 Gradle 8.0 扫清障碍。

不过,有一些可能需要你关注的潜在破坏性变更:

目标配置

  • org.jetbrains.kotlin.gradle.dsl.SingleTargetExtension 现在有一个泛型参数 SingleTargetExtension<T : KotlinTarget>。
  • kotlin.targets.fromPreset() 约定已被弃用。你仍然可以使用 kotlin.targets { fromPreset() },但我们建议显式设置目标。
  • Gradle 自动生成的目标访问器在 kotlin.targets { } 块内不再可用。请改用 findByName("targetName") 方法。

请注意,这类访问器在 kotlin.targets 的情况下仍然可用,例如 kotlin.targets.linuxX64。

源目录配置

Kotlin Gradle 插件现在把 Kotlin SourceDirectorySet 作为 kotlin 扩展添加到 Java 的 SourceSet 组中。这使得你可以在 build.gradle.kts 文件中像配置 Java、Groovy 和 Scala那样配置源目录:

1
2
3
4
5
6
7
8
sourceSets {
    main {
        kotlin {
            java.setSrcDirs(listOf("src/java"))
            kotlin.setSrcDirs(listOf("src/kotlin"))
        }
    }
}

你不再需要使用已弃用的 Gradle 约定来为 Kotlin 指定源目录。

请记住,你也可以使用 kotlin 扩展来访问 KotlinSourceSet:

1
2
3
4
5
6
7
kotlin {
    sourceSets {
        main {
        // ...
        }
    }
}

JVM 工具链配置的新方法

本次发布提供了一个新的 jvmToolchain() 方法,用于启用 JVM 工具链特性。如果你不需要任何额外的配置字段(例如 implementation 或 vendor),可以在 Kotlin 扩展中使用这个方法:

1
2
3
kotlin {
    jvmToolchain(17)
}

这简化了 Kotlin 项目设置过程,无需额外配置。在本次发布之前,你只能用以下方式指定 JDK 版本:

1
2
3
4
5
kotlin {
    jvmToolchain {
        languageVersion.set(JavaLanguageVersion.of(17))
    }
}

标准库

Kotlin 1.7.20 为 java.nio.file.Path 类提供了新的扩展函数,让你可以遍历文件树:

  • walk() 惰性遍历以指定路径为根的文件树。
  • fileVisitor() 让你可以单独创建 FileVisitor。FileVisitor 定义了遍历目录和文件时对它们的操作。
  • visitFileTree(fileVisitor: FileVisitor, ...) 消费一个现成的 FileVisitor,并在底层使用 java.nio.file.Files.walkFileTree()。
  • visitFileTree(..., builderAction: FileVisitorBuilder.() -> Unit) 用 builderAction 创建 FileVisitor 并调用 visitFileTree(fileVisitor, ...) 函数。
  • FileVisitResult 是 FileVisitor 的返回类型,其默认值为 CONTINUE,表示继续处理文件。

警告: java.nio.file.Path 的新扩展函数是实验性的。它们可能随时发生变化。需要选择启用(详见下文),并且你应仅将它们用于评估目的。

下面是你用这些新扩展函数可以做的一些事情:

  • 显式创建 FileVisitor 然后使用:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
  val cleanVisitor = fileVisitor {
      onPreVisitDirectory { directory, attributes ->
          // 访问目录时的某些逻辑
          FileVisitResult.CONTINUE
      }

      onVisitFile { file, attributes ->
          // 访问文件时的某些逻辑
          FileVisitResult.CONTINUE
      }
  }

  // 某些逻辑可以放在这里

  projectDirectory.visitFileTree(cleanVisitor)
  • 用 builderAction 创建 FileVisitor 并立即使用:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  projectDirectory.visitFileTree {
  // 定义 builderAction:
      onPreVisitDirectory { directory, attributes ->
          // 访问目录时的某些逻辑
          FileVisitResult.CONTINUE
      }

      onVisitFile { file, attributes ->
          // 访问文件时的某些逻辑
          FileVisitResult.CONTINUE
      }
  }
  • 用 walk() 函数遍历以指定路径为根的文件树:
 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
40
41
42
43
44
45
46
  @OptIn(kotlin.io.path.ExperimentalPathApi::class)
  fun traverseFileTree() {
      val cleanVisitor = fileVisitor {
          onPreVisitDirectory { directory, _ ->
              if (directory.name == "build") {
                  directory.toFile().deleteRecursively()
                  FileVisitResult.SKIP_SUBTREE
              } else {
                  FileVisitResult.CONTINUE
              }
          }

          onVisitFile { file, _ ->
              if (file.extension == "class") {
                  file.deleteExisting()
              }
              FileVisitResult.CONTINUE
          }
      }

      val rootDirectory = createTempDirectory("Project")

      rootDirectory.resolve("src").let { srcDirectory ->
          srcDirectory.createDirectory()
          srcDirectory.resolve("A.kt").createFile()
          srcDirectory.resolve("A.class").createFile()
      }

      rootDirectory.resolve("build").let { buildDirectory ->
          buildDirectory.createDirectory()
          buildDirectory.resolve("Project.jar").createFile()
      }

  // 使用 walk 函数:
      val directoryStructure = rootDirectory.walk(PathWalkOption.INCLUDE_DIRECTORIES)
          .map { it.relativeTo(rootDirectory).toString() }
          .toList().sorted()
      assertPrints(directoryStructure, "[, build, build/Project.jar, src, src/A.class, src/A.kt]")

      rootDirectory.visitFileTree(cleanVisitor)

      val directoryStructureAfterClean = rootDirectory.walk(PathWalkOption.INCLUDE_DIRECTORIES)
          .map { it.relativeTo(rootDirectory).toString() }
          .toList().sorted()
      assertPrints(directoryStructureAfterClean, "[, src, src/A.kt]")
  }

与实验性 API 的惯例一样,这些新扩展需要选择启用:@OptIn(kotlin.io.path.ExperimentalPathApi::class) 或 @kotlin.io.path.ExperimentalPathApi。或者,你也可以使用编译器选项:-opt-in=kotlin.io.path.ExperimentalPathApi。

我们非常欢迎你在 YouTrack 上提供关于 walk() 函数和 visit 扩展函数的反馈。

文档更新

自上一个版本以来,Kotlin 文档有一些值得注意的变化:

改版和改进的页面

  • 基本类型概览 —— 了解 Kotlin 中使用的基本类型:数值、布尔值、字符、字符串、数组和无符号整数。
  • 用于 Kotlin 开发的 IDE —— 查看官方支持 Kotlin 的 IDE 列表,以及具有社区支持插件的工具。

Kotlin Multiplatform 专栏的新文章

新增和更新的教程

发行文档的变化

我们不再为每个版本提供推荐的 kotlinx 库列表。该列表只包含推荐并与 Kotlin 本身一起测试过的版本,没有考虑到有些库相互依赖并需要特定的 kotlinx 版本,该版本可能与推荐的 Kotlin 版本不同。

我们正在寻找一种方式来提供关于库之间如何相互关联和依赖的信息,以便在你升级项目中的 Kotlin 版本时,能够清楚地知道应该使用哪个 kotlinx 库版本。

安装 Kotlin 1.7.20

IntelliJ IDEA 2021.3、2022.1 和 2022.2 会自动建议把 Kotlin 插件更新到 1.7.20。

注意: 对于 Android Studio Dolphin(213)、Electric Eel(221)和 Flamingo(222),Kotlin 插件 1.7.20 将随即将发布的 Android Studio 更新一同提供。

新的命令行编译器可在 GitHub 发布页面下载。

Kotlin 1.7.20 兼容性指南

尽管 Kotlin 1.7.20 是一个增量版本,但为限制 Kotlin 1.7.0 中引入问题的影响范围,我们仍做出了一些不兼容变更。

这些变更的详细列表请见 Kotlin 1.7.20 兼容性指南。