13.6.8.2 Kotlin 1.6.0 的新变化
16 分钟阅读
13.6.8.2 Kotlin 1.6.0 的新变化
阅读 Kotlin 1.6.0 的发行说明,涵盖新的语言特性、Kotlin Multiplatform、JVM、Native、JS 的更新,以及对 Gradle 和 Maven 构建工具的支持。
Kotlin 1.6.0 引入了新的语言特性、对现有特性的优化和改进,并对 Kotlin 标准库做了大量改进。
你也可以在发布博客文章中查看变更概览。
提示: 关于 Kotlin 发布周期的信息,请参阅 Kotlin 发布流程。
语言
Kotlin 1.6.0 把此前在 1.5.30 中作为预览引入的若干语言特性稳定下来:
它还包含各种类型推断改进以及对类类型参数上注解的支持:
针对 enum、sealed 和 Boolean 主语的稳定穷尽 when 语句
穷尽的 when 语句包含针对其主语所有可能类型或值的分支,或者包含针对某些类型的分支再加上一个 else 分支。它覆盖所有可能情况,让你的代码更安全。
我们很快将禁止非穷尽的 when 语句,使行为与 when 表达式保持一致。为确保平滑迁移,Kotlin 1.6.0 会对主语为 enum、sealed 或 Boolean 的非穷尽 when 语句报告警告。这些警告将在未来的版本中变为错误。
| |
关于该变更及其影响的更详细说明,请参阅这个 YouTrack 议题。
把挂起函数作为父类型的稳定支持
挂起函数类型的实现已在 Kotlin 1.6.0 中变为稳定。此前已在 1.5.30 中提供预览。
在设计使用 Kotlin 协程并接受挂起函数类型的 API 时,该特性会很有用。你现在可以把所需行为封装在实现了挂起函数类型的单独类中,从而简化代码。
| |
此前只允许 lambda 和挂起函数引用的地方,现在可以使用该类的实例:launchOnClick(MyClickAction())。
目前由于实现细节存在两个限制:
- 你不能在父类型列表中混用普通函数类型和挂起函数类型。
- 你不能使用多个挂起函数父类型。
稳定的挂起转换
Kotlin 1.6.0 引入了从普通函数类型到挂起函数类型的稳定转换。从 1.4.0 起,该特性支持函数字面量和可调用引用。在 1.6.0 中,它对任何形式的表达式都有效。作为调用实参,你现在可以在期望挂起函数的位置传入任何合适的普通函数类型表达式。编译器会自动执行隐式转换。
| |
注解类的稳定实例化
Kotlin 1.5.30 在 JVM 平台上引入了对注解类实例化的实验性支持。在 1.6.0 中,该特性对 Kotlin/JVM 和 Kotlin/JS 都默认可用。
在这个 KEEP 中进一步了解注解类的实例化。
改进递归泛型类型的类型推断
Kotlin 1.5.30 引入了对递归泛型类型类型推断的改进,使它们的类型实参可以仅根据相应类型参数的上界推断出来。该改进此前需要编译器选项才能使用。在 1.6.0 及更高版本中,它默认启用。
| |
构建器推断的变化
构建器推断是一种类型推断形式,在调用泛型构建器函数时很有用。它可以借助其 lambda 实参内部调用所携带的类型信息来推断调用的类型实参。
我们做了多项改动,使我们更接近完全稳定的构建器推断。从 1.6.0 开始:
- 你可以在构建器 lambda 内部进行返回尚未推断出类型实例的调用,而无需指定在 1.5.30 中引入的
-Xunrestricted-builder-inference编译器选项。 - 使用
-Xenable-builder-inference,你可以在不应用@BuilderInference注解的情况下编写自己的构建器。
警告: 请注意,这些构建器的使用方也需要指定相同的
-Xenable-builder-inference编译器选项。
- 使用
-Xenable-builder-inference时,如果普通类型推断无法获得足够的类型信息,构建器推断会自动激活。
支持类类型参数上的注解
对类类型参数上注解的支持如下所示:
| |
所有类型参数上的注解都会被写入 JVM 字节码,因此注解处理器可以使用它们。
关于其动机用例,请阅读这个 YouTrack 议题。
进一步了解注解。
更长时间支持以前的 API 版本
从 Kotlin 1.6.0 开始,除了当前的稳定版本之外,我们将支持针对三个以前的 API 版本进行开发,而不是两个。目前我们支持 1.3、1.4、1.5 和 1.6 版本。
Kotlin/JVM
对于 Kotlin/JVM,从 1.6.0 开始,编译器可以生成字节码版本对应 JVM 17 的类。新的语言版本还包含优化的委托属性和可重复注解,这些都在我们的路线图上:
面向 1.8 JVM 目标的带运行时保留策略的可重复注解
Java 8 引入了可重复注解,它可以多次应用于同一个代码元素。该特性要求 Java 代码中存在两个声明:用 @java.lang.annotation.Repeatable 标记的可重复注解本身,以及用于存放其值的容器注解。
Kotlin 也有可重复注解,但只需要在注解声明上使用 @kotlin.annotation.Repeatable 即可使其可重复。在 1.6.0 之前,该特性只支持 SOURCE 保留策略,并且与 Java 的可重复注解不兼容。Kotlin 1.6.0 移除了这些限制。@kotlin.annotation.Repeatable 现在接受任何保留策略,并使注解在 Kotlin 和 Java 中都可重复。Java 的可重复注解现在也可以从 Kotlin 一侧使用。
虽然你可以声明容器注解,但并非必须。例如:
- 如果注解
@Tag使用了@kotlin.annotation.Repeatable标记,Kotlin 编译器会自动生成一个名为@Tag.Container的容器注解类:
| |
- 要为容器注解设置自定义名称,请应用
@kotlin.jvm.JvmRepeatable元注解,并把显式声明的容器注解类作为实参传入:
| |
Kotlin 反射现在通过新函数 KAnnotatedElement.findAnnotations() 同时支持 Kotlin 和 Java 的可重复注解。
在这个 KEEP 中进一步了解 Kotlin 可重复注解。
优化对给定 KProperty 实例调用 get/set 的委托属性
我们通过省略 $delegate 字段并生成对所引用属性的直接访问,优化了生成的 JVM 字节码。
例如,在以下代码中
| |
Kotlin 不再生成 content$delegate 字段。content 变量的属性访问器会直接调用 impl 变量,跳过委托属性的 getValue/setValue 运算符,从而避免需要 KProperty 类型的属性引用对象。
感谢我们 Google 的同事完成了这一实现!
进一步了解委托属性。
Kotlin/Native
Kotlin/Native 获得了多项改进和组件更新,其中一些处于预览状态:
- 新内存管理器的预览
- 支持 Xcode 13
- 在任何宿主上编译 Windows 目标
- LLVM 与链接器更新
- 性能改进
- 与 JVM、JS IR 后端统一的编译器插件 ABI
- klib 链接失败的详细错误信息
- 重做未处理异常的处理 API
新内存管理器的预览
警告: 新的 Kotlin/Native 内存管理器是实验性的。它可能随时被移除或更改。需要选择启用(详见下文),并且你应仅将其用于评估目的。我们非常欢迎你在 YouTrack 上提供反馈。
在 Kotlin 1.6.0 中,你可以试用新的 Kotlin/Native 内存管理器的开发预览版。它让我们更接近消除 JVM 与 Native 平台之间的差异,从而在多平台项目中提供一致的开发者体验。
其中一项值得注意的变化是顶层属性的惰性初始化,与 Kotlin/JVM 相同。当同一文件中某个顶层属性或函数第一次被访问时,该顶层属性才会被初始化。该模式还包含全局过程间优化(仅对 release 二进制文件启用),它会移除冗余的初始化检查。
我们最近发布了一篇关于新内存管理器的博客文章。阅读它可以了解新内存管理器的当前状态并找到一些演示项目,或者直接查看迁移说明自行试用。请检查新内存管理器在你的项目中的表现,并在我们的问题跟踪器 YouTrack 中分享反馈。
支持 Xcode 13
Kotlin/Native 1.6.0 支持 Xcode 13 —— 最新版本的 Xcode。你可以放心更新 Xcode,继续开发面向 Apple 操作系统的 Kotlin 项目。
注意: Xcode 13 中新增的库在 Kotlin 1.6.0 中不可用,但我们会在即将发布的版本中添加对它们的支持。
在任何宿主上编译 Windows 目标
从 1.6.0 开始,你不再需要 Windows 宿主来编译 Windows 目标 mingwX64 和 mingwX86。它们可以在任何支持 Kotlin/Native 的宿主上编译。
LLVM 与链接器更新
我们重做了 Kotlin/Native 底层使用的 LLVM 依赖。这带来了诸多好处,包括:
- 把 LLVM 版本更新到 11.1.0。
- 减小依赖体积。例如在 macOS 上,它现在约为 300 MB,而之前版本约为 1200 MB。
- 排除了对
ncurses5库的依赖,该库在现代 Linux 发行版中不可用。
除 LLVM 更新之外,Kotlin/Native 现在为 MinGW 目标使用 LLD 链接器(LLVM 项目提供的链接器)。相比此前使用的 ld.bfd 链接器,它带来诸多好处,并将让我们能够改善所生成二进制文件的运行时性能,并为 MinGW 目标支持编译器缓存。请注意,LLD 要求使用导入库进行 DLL 链接。在这个 Stack Overflow 讨论串中了解更多。
性能改进
Kotlin/Native 1.6.0 带来以下性能改进:
- 编译时间:
linuxX64和iosArm64目标默认启用编译器缓存。这会加快大多数 debug 模式下的编译(首次编译除外)。测量显示,在我们的测试项目上速度提升约 200%。这些目标的编译器缓存自 Kotlin 1.5.0 起就可通过额外的 Gradle 属性使用;现在你可以移除它们了。 - 运行时:得益于所生成 LLVM 代码中的优化,使用
for循环遍历数组现在最快可提升 12%。
与 JVM、JS IR 后端统一的编译器插件 ABI
警告: 为 Kotlin/Native 使用通用 IR 编译器插件 ABI 的选项是实验性的。它可能随时被移除或更改。需要选择启用(详见下文),并且你应仅将其用于评估目的。我们非常欢迎你在 YouTrack 上提供反馈。
在此前的版本中,由于 ABI 的差异,编译器插件的作者必须为 Kotlin/Native 提供单独的产物。
从 1.6.0 开始,Kotlin Multiplatform Gradle 插件能够为 Kotlin/Native 使用可嵌入的编译器 jar —— 即用于 JVM 和 JS IR 后端的那个 jar。这是朝着统一编译器插件开发体验迈出的一步,因为你现在可以对接 Native 和其他受支持平台使用相同的编译器插件产物。
这是此类支持的预览版本,需要选择启用。要开始为 Kotlin/Native 使用通用的编译器插件产物,请在 gradle.properties 中添加以下行:kotlin.native.useEmbeddableCompilerJar=true。
我们计划将来默认为 Kotlin/Native 使用可嵌入的编译器 jar,因此了解该预览版对你的效果对我们至关重要。
如果你是编译器插件的作者,请试用该模式并检查它对你的插件是否有效。请注意,取决于插件的结构,可能需要迁移步骤。迁移说明请参阅这个 YouTrack 议题,并在评论中留下你的反馈。
klib 链接失败的详细错误信息
Kotlin/Native 编译器现在会为 klib 链接错误提供详细的错误信息。这些消息现在有清晰的错误说明,还包含可能的原因以及修复方法的信息。
例如:
- 1.5.30:
| |
- 1.6.0:
| |
重做未处理异常的处理 API
我们在整个 Kotlin/Native 运行时中统一了未处理异常的处理方式,并把默认处理作为函数 processUnhandledException(throwable: Throwable) 暴露出来,供 kotlinx.coroutines 等自定义执行环境使用。这种处理也适用于从 Worker.executeAfter() 中的操作逃逸的异常,但仅适用于新的内存管理器。
API 改进还影响了由 setUnhandledExceptionHook() 设置的钩子。此前,在 Kotlin/Native 运行时以未处理异常调用该钩子之后,这类钩子会被重置,并且程序总会随即终止。现在这些钩子可以被多次使用;如果你希望程序在遇到未处理异常时总是终止,要么不要设置未处理异常钩子(setUnhandledExceptionHook()),要么确保在钩子末尾调用 terminateWithUnhandledException()。这有助于你把异常发送到第三方崩溃报告服务(例如 Firebase Crashlytics),然后终止程序。从 main() 逃逸的异常以及跨越互操作边界的异常总会终止程序,即使钩子没有调用 terminateWithUnhandledException()。
Kotlin/JS
我们正在继续努力稳定 Kotlin/JS 编译器的 IR 后端。Kotlin/JS 现在有一个禁用下载 Node.js 和 Yarn 的选项。
使用预装 Node.js 和 Yarn 的选项
现在,在构建 Kotlin/JS 项目时,你可以禁用下载 Node.js 和 Yarn,改用宿主机上已安装的实例。这对于在无互联网连接的服务器(例如 CI 服务器)上构建很有用。
要禁用下载外部组件,请在 build.gradle(.kts) 中添加以下几行:
- Yarn:
Kotlin
| |
Groovy
| |
- Node.js:
Kotlin
| |
Groovy
| |
Kotlin Gradle 插件
在 Kotlin 1.6.0 中,我们把 KotlinGradleSubplugin 类的弃用级别改为 ‘ERROR’。该类用于编写编译器插件。在后续版本中,我们将移除该类。请改用 KotlinCompilerPluginSupportPlugin 类。
我们移除了 kotlin.useFallbackCompilerSearch 构建选项以及 noReflect 和 includeRuntime 编译器选项。useIR 编译器选项已被隐藏,并将在即将发布的版本中移除。
在 Kotlin Gradle 插件中进一步了解当前受支持的编译器选项。
标准库
标准库的 1.6.0 新版本稳定了实验性特性、引入了新特性,并统一了跨平台的行为:
- 新的 readline 函数
- 稳定的 typeOf()
- 稳定的集合构建器
- 稳定的 Duration API
- 把 Regex 拆分为序列
- 整数上的位旋转运算
- JS 中 replace() 和 replaceFirst() 的变化
- 对现有 API 的改进
- 弃用
新的 readline 函数
Kotlin 1.6.0 提供了用于处理标准输入的新函数:readln() 和 readlnOrNull()。
注意: 目前,新函数仅在 JVM 和 Native 目标平台上可用。
|早期版本|1.6.0 替代方案|用法|
|readLine()!!|readln()| 从 stdin 读取一行并返回它;如果已到达 EOF 则抛出 RuntimeException。 |
|readLine()|readlnOrNull()| 从 stdin 读取一行并返回它;如果已到达 EOF 则返回 null。 |
我们认为,去掉读取一行时使用 !! 的需要会改善新手的体验并简化 Kotlin 教学。为了让读行操作的名称与 println() 对仗,我们决定把新函数的名称缩短为 ’ln’。
| |
| |
现有的 readLine() 函数在你的 IDE 代码补全中会获得比 readln() 和 readlnOrNull() 更低的优先级。IDE 检查也会建议使用新函数而不是旧的 readLine()。
我们计划在未来的版本中逐步弃用 readLine() 函数。
稳定的 typeOf()
1.6.0 版本带来了稳定的 typeOf() 函数,完成了路线图上的主要项目之一。
自 1.3.40 起,typeOf() 在 JVM 平台上作为实验性 API 可用。现在你可以在任何 Kotlin 平台使用它,并获得编译器能够推断的任何 Kotlin 类型的 KType 表示:
| |
稳定的集合构建器
在 Kotlin 1.6.0 中,集合构建器函数被提升为稳定。集合构建器返回的集合现在在其只读状态下是可序列化的。
你现在可以在不添加选择启用注解的情况下使用 buildMap()、buildList() 和 buildSet():
| |
稳定的 Duration API
用于以不同时间单位表示时长的 Duration 类已被提升为稳定。在 1.6.0 中,Duration API 有以下变化:
把时长分解为天、小时、分钟、秒和纳秒的
toComponents()函数的第一个组成部分现在具有Long类型而不是Int。此前,如果该值超出Int范围,它会被强制转换到该范围内。使用Long类型后,你可以分解时长范围内的任何值,而不会截断超出Int的值。DurationUnit枚举现在是独立的,不再是 JVM 上java.util.concurrent.TimeUnit的类型别名。我们没有发现typealias DurationUnit = TimeUnit有任何有说服力的使用场景。此外,通过类型别名暴露TimeUnitAPI 可能会让DurationUnit的使用者困惑。应社区反馈,我们恢复了
Int.seconds之类的扩展属性。但我们希望限制它们的适用范围,因此把它们放进了Duration类的伴生对象中。虽然 IDE 仍然可以在补全中建议这些扩展并自动插入来自伴生对象的 import,但将来我们计划把该行为限制在期望Duration类型的情况下。
| |
我们建议把此前引入的伴生函数(例如 Duration.seconds(Int))以及已弃用的顶层扩展(如 Int.seconds)替换为 Duration.Companion 中的新扩展。
注意: 这种替换可能在旧顶层扩展与新伴生扩展之间造成歧义。在执行自动迁移之前,请务必使用
kotlin.time包的通配符导入 ——import kotlin.time.*。
把 Regex 拆分为序列
Regex.splitToSequence(CharSequence) 和 CharSequence.splitToSequence(Regex) 函数已被提升为稳定。它们会围绕给定正则的匹配位置拆分字符串,但把结果作为 Sequence 返回,因此对该结果的所有操作都是惰性执行的:
| |
整数上的位旋转运算
在 Kotlin 1.6.0 中,用于位操作的 rotateLeft() 和 rotateRight() 函数变为稳定。这些函数把数字的二进制表示按指定的位数向左或向右旋转:
| |
JS 中 replace() 和 replaceFirst() 的变化
在 Kotlin 1.6.0 之前,当替换字符串包含分组引用时,replace() 和 replaceFirst() 这两个 Regex 函数在 Java 和 JS 中的行为不同。为使所有目标平台上的行为一致,我们修改了它们在 JS 中的实现。
替换字符串中出现的 ${name} 或 $index 会被替换为与指定索引或名称的捕获组相对应的子序列:
$index—— ‘$’ 之后的第一个数字总是被视为分组引用的一部分。后续数字只有在构成有效的分组引用时才会并入index。只有数字 ‘0’–‘9’ 会被视为分组引用的潜在组成部分。请注意,捕获组的索引从 ‘1’ 开始。索引为 ‘0’ 的组表示整个匹配。${name}——name可以由拉丁字母 ‘a’–‘z’、‘A’–‘Z’ 或数字 ‘0’–‘9’ 组成。第一个字符必须是字母。
注意: 替换模式中的具名分组目前仅在 JVM 上受支持。
- 要把后一个字符作为字面量包含在替换字符串中,请使用反斜杠字符
\:
| |
如果替换字符串必须被视为字面量字符串,你可以使用 Regex.escapeReplacement()。
对现有 API 的改进
- 1.6.0 版本为
Comparable.compareTo()添加了中缀扩展函数。现在你可以使用中缀形式比较两个对象的顺序:
| |
- JS 中的
Regex.replace()现在也不再是内联函数,以统一其跨平台实现。 compareTo()和equals()这两个 String 函数,以及isBlank()这个 CharSequence 函数在 JS 中的行为现在与 JVM 完全一致。此前它们在处理非 ASCII 字符时存在偏差。
弃用
在 Kotlin 1.6.0 中,我们以警告形式为一些仅 JS 的标准库 API 启动弃用周期。
concat()、match() 和 matches() 字符串函数
- 要把字符串与给定其他对象的字符串表示拼接,请使用
plus()而不是concat()。 - 要查找正则表达式在输入中的所有匹配,请使用 Regex 类的
findAll()而不是String.match(regex: String)。 - 要检查正则表达式是否匹配整个输入,请使用 Regex 类的
matches()而不是String.matches(regex: String)。
接受比较函数的数组 sort()
我们弃用了 Array<out T>.sort() 函数以及内联函数 ByteArray.sort()、ShortArray.sort()、IntArray.sort()、LongArray.sort()、FloatArray.sort()、DoubleArray.sort() 和 CharArray.sort(),它们按照比较函数传入的顺序对数组排序。请使用其他标准库函数进行数组排序。
参考集合排序一节。
工具
Kover —— Kotlin 的代码覆盖率工具
警告: Kover Gradle 插件是实验性的。我们非常欢迎你在 GitHub 上提供反馈。
随着 Kotlin 1.6.0 的发布,我们引入 Kover —— 一个适用于 IntelliJ 和 JaCoCo Kotlin 代码覆盖率代理的 Gradle 插件。它适用于所有语言结构,包括内联函数。
在其 GitHub 仓库或这个视频中进一步了解 Kover:
视频:Kover – The Code Coverage Plugin
协程 1.6.0-RC
kotlinx.coroutines 1.6.0-RC 已发布,带来多项特性和改进:
- 支持新的 Kotlin/Native 内存管理器
- 引入调度器 views API,它允许在不创建额外线程的情况下限制并行度
- 从 Java 6 目标迁移到 Java 8 目标
kotlinx-coroutines-test采用重做后的新 API 并支持多平台- 引入
CopyableThreadContextElement,它让协程可以线程安全地写入ThreadLocal变量
在更新日志中了解更多。
迁移到 Kotlin 1.6.0
Kotlin 插件 1.6.0 一发布,IntelliJ IDEA 和 Android Studio 就会建议更新到它。
要把现有项目迁移到 Kotlin 1.6.0,请把 Kotlin 版本改为 1.6.0 并重新导入你的 Gradle 或 Maven 项目。了解如何更新到 Kotlin 1.6.0。
要用 Kotlin 1.6.0 开始新项目,请更新 Kotlin 插件并通过 File | New | Project 运行项目向导。
新的命令行编译器可在 GitHub 发布页面下载。
Kotlin 1.6.0 是一个功能版本,因此可能带来与为更早版本语言编写的代码不兼容的变更。在 Kotlin 1.6 兼容性指南中查找这些变更的详细列表。