13.6.9.1 Kotlin 1.5.30 的新变化
17 分钟阅读
13.6.9.1 Kotlin 1.5.30 的新变化
阅读 Kotlin 1.5.30 的发行说明,涵盖新的语言特性、Kotlin Multiplatform、JVM、Native、JS 的更新,以及对 Gradle 和 Maven 构建工具的支持。
Kotlin 1.5.30 带来了语言更新,包括未来变更的预览、平台支持和工具方面的各种改进,以及新的标准库函数。
以下是一些主要改进:
- 语言特性,包括实验性的密封
when语句、选择启用要求的使用方式的变更等 - 对 Apple 芯片的原生支持
- Kotlin/JS IR 后端达到 Beta
- 改进的 Gradle 插件体验
你也可以在发布博客文章和这个视频中查看变更简介:
提示: 关于 Kotlin 发布周期的信息,请参阅 Kotlin 发布流程。
语言特性
Kotlin 1.5.30 展示了未来语言变更的预览,并改进了选择启用要求机制和类型推断:
- 针对 sealed 和 Boolean 主语的穷尽 when 语句
- 把挂起函数作为父类型
- 对实验性 API 的隐式使用要求选择启用
- 在不同目标上使用选择启用要求注解的变化
- 改进递归泛型类型的类型推断
- 消除构建器推断限制
针对 sealed 和 Boolean 主语的穷尽 when 语句
警告: 对密封(穷尽)when 语句的支持是实验性的。它可能随时被移除或更改。需要选择启用(详见下文),并且你应仅将其用于评估目的。我们非常欢迎你在 YouTrack 上提供反馈。
穷尽的 when 语句包含针对其主语所有可能类型或值的分支,或者包含针对某些类型的分支并加上一个 else 分支以覆盖其余情况。
我们计划很快禁止非穷尽的 when 语句,使行为与 when 表达式保持一致。为确保平滑迁移,你可以配置编译器对主语为密封类或 Boolean 的非穷尽 when 语句报告警告。这类警告将在 Kotlin 1.6 中默认出现,并在之后变为错误。
注意: 枚举已经会给出警告。
| |
要在 Kotlin 1.5.30 中启用该特性,请使用语言版本 1.6。你也可以通过启用渐进模式把警告变为错误。
Kotlin
| |
Groovy
| |
把挂起函数作为父类型
警告: 把挂起函数作为父类型的支持是实验性的。它可能随时被移除或更改。需要选择启用(详见下文),并且你应仅将其用于评估目的。我们非常欢迎你在 YouTrack 上提供反馈。
Kotlin 1.5.30 预览了把 suspend 函数类型用作父类型的能力,并带有一些限制。
| |
使用 -language-version 1.6 编译器选项启用该特性:
Kotlin
| |
Groovy
| |
该特性有以下限制:
- 你不能把普通函数类型和
suspend函数类型混用作父类型。这是因为 JVM 后端中suspend函数类型的实现细节:它们在 JVM 中表示为带标记接口的普通函数类型。由于这个标记接口,无法判断哪些父接口是挂起的、哪些是普通的。 - 你不能使用多个
suspend函数父类型。如果存在类型检查,你也不能使用多个普通函数父类型。
对实验性 API 的隐式使用要求选择启用
警告: 选择启用要求机制是实验性的。它可能随时发生变化。查看如何选择启用。请仅将其用于评估目的。我们非常欢迎你在 YouTrack 上提供反馈。
库作者可以把实验性 API 标记为要求选择启用,以告知使用者其处于实验状态。当该 API 被使用时,编译器会给出警告或错误,并要求明确同意才能消除它。
在 Kotlin 1.5.30 中,编译器会把签名中包含实验性类型的任何声明都视为实验性的。也就是说,即使是对实验性 API 的隐式使用,也要求选择启用。例如,如果某个函数的返回类型被标记为实验性 API 元素,那么即使该声明没有显式标记为要求选择启用,使用该函数也需要你选择启用。
| |
进一步了解选择启用要求。
在不同目标上使用选择启用要求注解的变化
警告: 选择启用要求机制是实验性的。它可能随时发生变化。查看如何选择启用。请仅将其用于评估目的。我们非常欢迎你在 YouTrack 上提供反馈。
Kotlin 1.5.30 提出在不同目标上使用和声明选择启用要求注解的新规则。编译器现在对那些在编译期难以处理的用例报告错误。在 Kotlin 1.5.30 中:
- 禁止在引用处用选择启用要求注解标记局部变量和值参数。
- 只有当基本声明也被标记时,才允许标记覆盖。
- 禁止标记幕后字段和 getter。你可以改为标记基本属性。
- 禁止在选择启用要求注解的声明处设置
TYPE和TYPE_PARAMETER注解目标。
进一步了解选择启用要求。
改进递归泛型类型的类型推断
在 Kotlin 和 Java 中,你可以定义在类型参数中引用自身的递归泛型类型。在 Kotlin 1.5.30 中,如果该类型是递归泛型,Kotlin 编译器可以只根据相应类型参数的上界推断出类型实参。这使得用递归泛型类型创建各种模式成为可能,而这些模式在 Java 中常用于构建构建器 API。
| |
你可以通过传入 -Xself-upper-bound-inference 或 -language-version 1.6 编译器选项来启用这些改进。新支持用例的其他示例请见这个 YouTrack 议题。
消除构建器推断限制
构建器推断是一种特殊的类型推断,它允许你根据其 lambda 实参内部其他调用的类型信息推断出某次调用的类型实参。在调用 buildList() 或 sequence() 之类的泛型构建器函数时,这很有用:buildList { add("string") }。
在这类 lambda 实参内部,此前对使用构建器推断试图推断的类型信息有限制。这意味着你只能指定它,而不能获取它。例如,如果不显式指定类型实参,你就不能在 buildList() 的 lambda 实参内部调用 get()。
Kotlin 1.5.30 通过 -Xunrestricted-builder-inference 编译器选项移除了这些限制。添加该选项即可在泛型构建器函数的 lambda 实参内部启用此前被禁止的调用:
| |
此外,你也可以用 -language-version 1.6 编译器选项启用该特性。
Kotlin/JVM
在 Kotlin 1.5.30 中,Kotlin/JVM 获得以下特性:
Kotlin Gradle 插件在 JVM 平台上的更新请见 Gradle 一节。
注解类的实例化
警告: 注解类的实例化是实验性的。它可能随时被移除或更改。需要选择启用(详见下文),并且你应仅将其用于评估目的。我们非常欢迎你在 YouTrack 上提供反馈。
在 Kotlin 1.5.30 中,你现在可以在任意代码中调用注解类的构造器来获得一个实例。该特性覆盖的用例与允许实现注解接口的 Java 约定相同。
| |
使用 -language-version 1.6 编译器选项启用该特性。请注意,当前注解类的所有限制仍然有效,例如对非 val 参数的限制,或除次构造器之外不能有成员的限制。
在这个 KEEP 中进一步了解注解类的实例化
改进可空性注解支持配置
Kotlin 编译器可以读取各种可空性注解,以从 Java 获取可空性信息。这些信息让它能够在调用 Java 代码时报告 Kotlin 中的可空性不匹配。
在 Kotlin 1.5.30 中,你可以指定编译器是否根据特定类型可空性注解的信息报告可空性不匹配。只需使用编译器选项 -Xnullability-annotations=@<package-name>:<report-level>。在该实参中指定完全限定的可空性注解包和以下报告级别之一:
ignore,忽略可空性不匹配warn,报告警告strict,报告错误。
请查看受支持可空性注解的完整列表以及它们的完全限定包名。
下面是一个示例,展示如何为新支持的 RxJava 3 可空性注解启用错误报告:-Xnullability-annotations=@io.reactivex.rxjava3.annotations:strict。请注意,这类可空性不匹配默认都是警告。
Kotlin/Native
Kotlin/Native 获得了各种变更和改进:
- Apple 芯片支持
- CocoaPods Gradle 插件改进的 Kotlin DSL
- 与 Swift 5.5 async/await 的实验性互操作
- 改进对象和伴生对象的 Swift/Objective-C 映射
- 弃用 MinGW 目标中不带导入库的 DLL 链接
Apple 芯片支持
Kotlin 1.5.30 引入了对 Apple 芯片的原生支持。
此前,Kotlin/Native 编译器和工具在 Apple 芯片宿主机上工作需要 Rosetta 转换环境。在 Kotlin 1.5.30 中,不再需要转换环境 —— 编译器和工具可以直接在 Apple 芯片硬件上运行,无需任何额外操作。
我们还引入了新目标,让 Kotlin 代码可以在 Apple 芯片上原生运行:
macosArm64iosSimulatorArm64watchosSimulatorArm64tvosSimulatorArm64
它们在 Intel 芯片和 Apple 芯片宿主机上都可用。所有现有目标在 Apple 芯片宿主机上也同样可用。
请注意,在 1.5.30 中,我们仅为 kotlin-multiplatform Gradle 插件中的 Apple 芯片目标提供基本支持。特别是新模拟器目标并未包含在 ios、tvos 和 watchos 目标快捷方式中。我们会继续努力改善使用这些新目标的用户体验。
CocoaPods Gradle 插件改进的 Kotlin DSL
Kotlin/Native framework 的新参数
Kotlin 1.5.30 为 Kotlin/Native framework 引入了改进的 CocoaPods Gradle 插件 DSL。除了 framework 的名称,你还可以在 Pod 配置中指定其他参数:
- 指定 framework 的动态或静态版本
- 显式启用导出依赖
- 启用 Bitcode 嵌入
要使用新的 DSL,请把项目更新到 Kotlin 1.5.30,并在 build.gradle(.kts) 文件的 cocoapods 部分指定参数:
| |
支持 Xcode 配置的自定义名称
Kotlin CocoaPods Gradle 插件支持 Xcode 构建配置中的自定义名称。如果你在 Xcode 中为构建配置使用了特殊名称(例如 Staging),它也能帮到你。
要指定自定义名称,请在 build.gradle(.kts) 文件的 cocoapods 部分使用 xcodeConfigurationToNativeBuildType 参数:
| |
该参数不会出现在 Podspec 文件中。当 Xcode 运行 Gradle 构建过程时,Kotlin CocoaPods Gradle 插件会选择所需的原生构建类型。
注意: 无需声明
Debug和Release配置,因为它们默认受支持。
与 Swift 5.5 async/await 的实验性互操作
警告: 与 Swift async/await 的并发互操作是实验性的。它可能随时被移除或更改。你应仅将其用于评估目的。我们非常欢迎你在 YouTrack 上提供反馈。
我们在 1.4.0 中增加了从 Objective-C 和 Swift 调用 Kotlin 挂起函数的支持,现在我们正在改进它,以跟上 Swift 5.5 的新特性 —— 使用 async 和 await 修饰符的并发。
对于返回类型可为空的挂起函数,Kotlin/Native 编译器现在会在生成的 Objective-C 头文件中生成 _Nullable_result 属性。这使得可以从 Swift 把它们作为具有正确可空性的 async 函数调用。
请注意,该特性是实验性的,未来可能受 Kotlin 和 Swift 双方变更的影响。目前我们提供的是该特性的预览版,它有一些限制,我们很想听听你的想法。关于其当前状态以及如何留下反馈,请参阅这个 YouTrack 议题。
改进对象和伴生对象的 Swift/Objective-C 映射
现在可以用对原生 iOS 开发者更直观的方式获取对象和伴生对象。例如,如果你在 Kotlin 中有以下对象:
| |
要在 Swift 中访问它们,你可以使用 shared 和 companion 属性:
| |
进一步了解 Swift/Objective-C 互操作。
弃用 MinGW 目标中不带导入库的 DLL 链接
LLD 是 LLVM 项目提供的链接器,由于其相较默认 ld.bfd 的优势(主要是更好的性能),我们计划开始在 Kotlin/Native 中为 MinGW 目标使用它。
不过,LLD 最新的稳定版本不支持为 MinGW(Windows)目标直接链接 DLL。这种链接需要使用导入库。虽然在 Kotlin/Native 1.5.30 中并不需要它们,但我们添加了一条警告,告知你这种用法与将来会成为 MinGW 默认链接器的 LLD 不兼容。
请在这个 YouTrack 议题中分享你对迁移到 LLD 链接器的想法和顾虑。
Kotlin Multiplatform
1.5.30 为 Kotlin Multiplatform 带来以下值得注意的更新:
在共享原生代码中使用自定义 cinterop 库
Kotlin Multiplatform 提供了在共享源集中使用平台相关互操作库的选项。在 1.5.30 之前,这只对随 Kotlin/Native 发行版提供的平台库有效。从 1.5.30 开始,你也可以对自定义 cinterop 库使用它。要启用该特性,请在 gradle.properties 中添加 kotlin.mpp.enableCInteropCommonization=true 属性:
kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.native.enableDependencyPropagation=false
kotlin.mpp.enableCInteropCommonization=true
支持 XCFramework
所有 Kotlin Multiplatform 项目现在都可以把 XCFramework 作为输出格式。Apple 引入 XCFramework 作为通用(fat)framework 的替代方案。借助 XCFramework,你可以:
- 把所有目标平台和架构的逻辑汇集到一个包中。
- 无需在把应用发布到 App Store 之前移除所有不必要的架构。
如果你想在 Apple M1 上同时为设备和模拟器使用 Kotlin framework,XCFramework 会很有用。
要使用 XCFramework,请更新你的 build.gradle(.kts) 脚本:
Kotlin
| |
Groovy
| |
当你声明 XCFramework 时,会注册以下新的 Gradle 任务:
assembleXCFrameworkassembleDebugXCFramework(额外的、包含 dSYM 的 debug 产物)assembleReleaseXCFramework
在这个 WWDC 视频中进一步了解 XCFramework。
Android 产物的新默认发布设置
使用 maven-publish Gradle 插件,你可以在构建脚本中指定 Android 变体名称,从而为 Android 目标发布多平台库。Kotlin Gradle 插件会自动生成发布产物。
在 1.5.30 之前,生成的发布元数据会为每个已发布的 Android 变体包含构建类型属性,使其只能与库使用方所使用的相同构建类型兼容。Kotlin 1.5.30 引入了新的默认发布设置:
- 如果项目发布的所有 Android 变体具有相同的构建类型属性,那么发布的变体将不再带有构建类型属性,从而与任何构建类型兼容。
- 如果已发布的变体具有不同的构建类型属性,那么只有值为
release的那些会在不带构建类型属性的情况下发布。这使 release 变体在使用方一侧与任何构建类型兼容,而非 release 变体只与匹配的使用方构建类型兼容。
要退出该行为并保留所有变体的构建类型属性,你可以设置这个 Gradle 属性:kotlin.android.buildTypeAttribute.keep=true。
Kotlin/JS
Kotlin/JS 在 1.5.30 中有两项重大改进:
JS IR 编译器后端达到 Beta
面向 Kotlin/JS 的基于 IR 的编译器后端在 1.4.0 中以 Alpha 形式引入,现在已达到 Beta。
此前我们发布了 JS IR 后端迁移指南,帮助你把自己的项目迁移到新后端。现在我们想介绍 Kotlin/JS Inspection Pack IDE 插件,它会在 IntelliJ IDEA 中直接显示所需的修改。
使用 Kotlin/JS IR 后端的应用获得更好的调试体验
Kotlin 1.5.30 为 Kotlin/JS IR 后端带来了 JavaScript source map 生成。在启用 IR 后端时,这将改善 Kotlin/JS 的调试体验,提供包括断点、单步执行和带正确源码引用的可读堆栈跟踪在内的完整调试支持。
了解如何在浏览器或 IntelliJ IDEA 中调试 Kotlin/JS。
Gradle
作为改善 Kotlin Gradle 插件用户体验这一使命的一部分,我们实现了以下特性:
- 支持 Java 工具链,其中包含为较早 Gradle 版本使用
UsesKotlinJavaToolchain接口指定 JDK 主目录的能力 - 更简便地显式指定 Kotlin 守护进程 JVM 参数的方式
支持 Java 工具链
Gradle 6.7 引入了“Java 工具链支持”特性。使用该特性,你可以:
- 使用与 Gradle 不同的 JDK 和 JRE 运行编译、测试和可执行文件。
- 使用尚未发布的语言版本编译和测试代码。
有了工具链支持,Gradle 可以自动检测本地 JDK 并安装 Gradle 构建所需的缺失 JDK。现在 Gradle 本身可以运行在任何 JDK 上,同时仍可复用构建缓存特性。
Kotlin Gradle 插件为 Kotlin/JVM 编译任务支持 Java 工具链。Java 工具链会:
- 设置可用于 JVM 目标的
jdkHome选项。
在用户没有显式设置
jvmTarget选项时,把kotlinOptions.jvmTarget设置为该工具链的 JDK 版本。如果没有配置工具链,jvmTarget字段使用默认值。进一步了解 JVM 目标兼容性。影响
kapt工作进程运行在哪个 JDK 上。
使用以下代码设置工具链。请把占位符 <MAJOR_JDK_VERSION> 替换为你希望使用的 JDK 版本:
Kotlin
| |
Groovy
| |
请注意,通过 kotlin 扩展设置工具链也会更新 Java 编译任务的工具链。
你可以通过 java 扩展设置工具链,Kotlin 编译任务会使用它:
| |
关于为 KotlinCompile 任务设置任意 JDK 版本的信息,请查看使用 Task DSL 设置 JDK 版本的文档。
对于 Gradle 6.1 到 6.6 版本,请使用 UsesKotlinJavaToolchain 接口设置 JDK 主目录。
使用 UsesKotlinJavaToolchain 接口指定 JDK 主目录
所有支持通过 kotlinOptions设置 JDK 的 Kotlin 任务现在都实现了 UsesKotlinJavaToolchain 接口。要设置 JDK 主目录,请填入你的 JDK 路径并替换 <JDK_VERSION> 占位符:
Kotlin
| |
Groovy
| |
对于 Gradle 6.1 到 6.6 版本,请使用 UsesKotlinJavaToolchain 接口。从 Gradle 6.7 开始,请改用 Java 工具链。
使用该特性时请注意,kapt 任务工作进程只会使用进程隔离模式,并且 kapt.workers.isolation 属性会被忽略。
更简便地显式指定 Kotlin 守护进程的 JVM 参数
在 Kotlin 1.5.30 中,Kotlin 守护进程 JVM 参数有了新的处理逻辑。以下列表中的每个选项都会覆盖其之前的选项:
- 如果什么都没指定,Kotlin 守护进程会像以前一样从 Gradle 守护进程继承参数。例如在
gradle.properties文件中:
org.gradle.jvmargs=-Xmx1500m -Xms=500m
- 如果 Gradle 守护进程的 JVM 参数中包含
kotlin.daemon.jvm.options系统属性,则像以前一样使用它:
org.gradle.jvmargs=-Dkotlin.daemon.jvm.options=-Xmx1500m -Xms=500m
- 你可以在
gradle.properties文件中添加kotlin.daemon.jvmargs属性:
kotlin.daemon.jvmargs=-Xmx1500m -Xms=500m
- 你可以在
kotlin扩展中指定参数:
Kotlin
| |
Groovy
| |
- 你可以为特定任务指定参数:
Kotlin
| |
Groovy
| |
注意: 在这种情况下,任务执行时可能会启动一个新的 Kotlin 守护进程实例。进一步了解 Kotlin 守护进程与 JVM 参数的交互。
关于 Kotlin 守护进程的更多信息,请参阅 Kotlin 守护进程以及如何在 Gradle 中使用它。
标准库
Kotlin 1.5.30 为标准库的 Duration 和 Regex API 带来改进:
修改 Duration.toString() 的输出
警告: Duration API 是实验性的。它可能随时被移除或更改。请仅将其用于评估目的。我们非常欢迎你在 YouTrack 上提供反馈。
在 Kotlin 1.5.30 之前,Duration.toString() 函数会以能产生最紧凑、最可读数值的单位返回其参数的字符串表示。从现在开始,它会返回一个由多个数值分量组合而成的字符串值,每个分量使用自己的单位。每个分量是一个数字后跟单位的缩写名:d、h、m、s。例如:
|函数调用示例|之前的输出|当前的输出|
Duration.days(45).toString()|45.0d|45d| Duration.days(1.5).toString()|36.0h|1d 12h| Duration.minutes(1230).toString()|20.5h|20h 30m| Duration.minutes(2415).toString()|40.3h|1d 16h 15m| Duration.minutes(920).toString()|920m|15h 20m| Duration.seconds(1.546).toString()|1.55s|1.546s| Duration.milliseconds(25.12).toString()|25.1ms|25.12ms|
负时长的表示方式也发生了变化。负时长以减号(-)为前缀,如果它由多个分量组成,则用括号括起来:-12m 和 -(1h 30m)。
请注意,小于一秒的小时长会用其中一个亚秒单位表示为单个数字。例如 ms(毫秒)、us(微秒)或 ns(纳秒):140.884ms、500us、24ns。不再使用科学计数法来表示它们。
如果你想用单一单位表示时长,请使用重载的 Duration.toString(unit, decimals) 函数。
注意: 在某些情况下(包括序列化和数据交换),我们建议使用
Duration.toIsoString()。Duration.toIsoString()使用更严格的 ISO-8601 格式,而不是Duration.toString()。
从 String 解析 Duration
警告: Duration API 是实验性的。它可能随时被移除或更改。请仅将其用于评估目的。我们非常欢迎你在这个 issue中提供反馈。
在 Kotlin 1.5.30 中,Duration API 中增加了新函数:
parse(),它支持解析以下函数的输出:toString()。toString(unit, decimals)。toIsoString()。parseIsoString(),它只解析toIsoString()产生的格式。parseOrNull()和parseIsoStringOrNull(),它们的行为与上面的函数相同,但在时长格式无效时返回null而不是抛出IllegalArgumentException。
下面是 parse() 和 parseOrNull() 的一些用法示例:
| |
下面是 parseIsoString() 和 parseIsoStringOrNull() 的一些用法示例:
| |
在特定位置用 Regex 匹配
警告:
Regex.matchAt()和Regex.matchesAt()函数是实验性的。它们可能随时被移除或更改。请仅将它们用于评估目的。我们非常欢迎你在 YouTrack 上提供反馈。
新的 Regex.matchAt() 和 Regex.matchesAt() 函数提供了一种方式来检查正则表达式是否在 String 或 CharSequence 的特定位置精确匹配。
matchesAt() 返回布尔结果:
| |
matchAt() 在找到匹配时返回该匹配,否则返回 null:
| |
把 Regex 拆分为序列
警告:
Regex.splitToSequence()和CharSequence.splitToSequence(Regex)函数是实验性的。它们可能随时被移除或更改。请仅将它们用于评估目的。我们非常欢迎你在 YouTrack 上提供反馈。
新的 Regex.splitToSequence() 函数是 split() 的惰性版本。它围绕给定正则的匹配位置拆分字符串,但把结果作为 Sequence 返回,因此对该结果的所有操作都是惰性执行的。
| |
CharSequence 上也添加了类似的函数:
| |
序列化 1.3.0-RC
kotlinx.serialization 1.3.0-RC 来了,带来新的 JSON 序列化能力:
- Java IO 流序列化
- 对默认值的属性级控制
- 从序列化中排除 null 值的选项
- 多态序列化中的自定义类判别器
在更新日志中了解更多。