13.6.9.3 Kotlin 1.5.0 新变化
15 分钟阅读
13.6.9.3 Kotlin 1.5.0 新变化
阅读 Kotlin 1.5.0 发行说明,了解新的语言特性,以及 Kotlin Multiplatform、JVM、Native、JS 的更新和 Gradle、Maven 的构建工具支持。
Kotlin 1.5.0 引入了新的语言特性、稳定的基于 IR 的 JVM 编译器后端、性能改进,以及稳定实验性特性和弃用陈旧特性等演进性变更。
你也可以在发布博客文章中查看这些变更的概述。
提示: 有关 Kotlin 发布周期的信息,请参见 Kotlin 发布流程。
语言特性
Kotlin 1.5.0 带来了在 1.4.30 中预览的新语言特性的稳定版本:
这些特性的详细说明可在这篇博客文章以及 Kotlin 文档的相应页面中找到。
支持 JVM record
Java 在不断快速演进,为了确保 Kotlin 与它保持互操作,我们为其最新特性之一——record 类——提供了支持。
Kotlin 对 JVM record 的支持包含双向互操作:
- 在 Kotlin 代码中,你可以像使用带属性的普通类那样使用 Java record 类。
- 要在 Java 代码中把 Kotlin 类当作 record 使用,请把它声明为
data类并用@JvmRecord注解标记。
| |
视频:Support for JVM Records in Kotlin 1.5.0
密封接口
Kotlin 接口现在可以带 sealed 修饰符,它对接口的作用方式与对类相同:密封接口的所有实现都在编译期已知。
| |
例如,你可以依靠这一事实编写穷尽的 when 表达式。
| |
此外,密封接口可以形成更灵活的受限类层次结构,因为一个类可以直接继承多个密封接口。
| |
视频:Sealed Interfaces and Sealed Classes Improvements
包级密封类层次结构
密封类现在可以在同一编译单元和同一包的所有文件中拥有子类。以前,所有子类都必须出现在同一个文件中。
直接子类可以是顶层的,也可以嵌套在任意数量的具名类、具名接口或具名对象中。
密封类的子类必须有正确限定的名称——它们不能是局部对象或匿名对象。
内联类
内联类是基于值的类的一个子集,只保存值。你可以把它们用作某种类型值的包装器,而不会带来使用内存分配的额外开销。
内联类可以在类名之前用 value 修饰符声明:
| |
JVM 后端还需要特殊的 @JvmInline 注解:
| |
inline 修饰符现在已被弃用并会产生警告。
视频:From Inline to Value Classes
Kotlin/JVM
Kotlin/JVM 获得了一些内部和面向用户的改进。以下是最值得注意的几项:
- 稳定的 JVM IR 后端
- 新的默认 JVM 目标:1.8
- 通过 invokedynamic 实现 SAM 适配器
- 通过 invokedynamic 实现 lambda
- 弃用 @JvmDefault 和旧的 Xjvm-default 模式
- 可空性注解处理的改进
稳定的 JVM IR 后端
Kotlin/JVM 编译器的基于 IR 的后端现在已进入 Stable,并默认启用。
从 Kotlin 1.4.0 开始,基于 IR 的后端的早期版本就可以预览,现在它已成为语言版本 1.5 的默认后端。对于更早的语言版本,仍然默认使用旧后端。
你可以在这篇博客文章中找到关于 IR 后端优势及其未来发展的更多细节。
如果你需要在 Kotlin 1.5.0 中使用旧后端,可以在项目的配置文件中添加以下代码:
- 在 Gradle 中:
Kotlin
| |
Groovy
| |
- 在 Maven 中:
| |
新的默认 JVM 目标:1.8
Kotlin/JVM 编译的默认目标版本现在是 1.8。1.6 目标已被弃用。
如果你需要为 JVM 1.6 构建,仍然可以切换到该目标。了解如何操作:
通过 invokedynamic 实现 SAM 适配器
Kotlin 1.5.0 现在使用动态调用(invokedynamic)来编译 SAM(单一抽象方法)转换:
- 当 SAM 类型是 Java 接口时,适用于任何表达式
- 当 SAM 类型是 Kotlin 函数式接口时,适用于 lambda
新实现使用 LambdaMetafactory.metafactory(),并且编译期间不再生成辅助包装类。这会减小应用 JAR 的体积,从而改善 JVM 启动性能。
要回退到基于匿名类生成的旧实现方案,请添加编译器选项 -Xsam-conversions=class。
请在 Gradle、Maven 和命令行编译器中了解如何添加编译器选项。
通过 invokedynamic 实现 lambda
警告: 把普通 Kotlin lambda 编译为 invokedynamic 是实验性的。它随时可能被放弃或更改。需要选择启用(详见下文),并且只应用于评估目的。我们欢迎你在 YouTrack 中提供反馈。
Kotlin 1.5.0 引入了对把普通 Kotlin lambda(未被转换为函数式接口实例)编译为动态调用(invokedynamic)的实验性支持。该实现通过使用 LambdaMetafactory.metafactory()在运行时有效地生成必要的类,从而产生更轻量的二进制文件。与普通的 lambda 编译相比,它目前有三个限制:
- 编译为 invokedynamic 的 lambda 不可序列化。
- 对此类 lambda 调用
toString()会得到可读性较差的字符串表示。 - 实验性的
reflectAPI 不支持用LambdaMetafactory创建的 lambda。
要试用该特性,请添加 -Xlambdas=indy 编译器选项。如果你能通过这个 YouTrack 议题分享反馈,我们将非常感激。
请在 Gradle、Maven 和命令行编译器中了解如何添加编译器选项。
弃用 @JvmDefault 和旧的 Xjvm-default 模式
在 Kotlin 1.4.0 之前,有 @JvmDefault 注解以及 -Xjvm-default=enable 和 -Xjvm-default=compatibility 模式。它们用于为 Kotlin 接口中特定的非抽象成员创建 JVM 默认方法。
在 Kotlin 1.4.0 中,我们引入了新的 Xjvm-default 模式,可以在整个项目范围内开启默认方法生成。
在 Kotlin 1.5.0 中,我们弃用 @JvmDefault 以及旧的 Xjvm-default 模式:-Xjvm-default=enable 和 -Xjvm-default=compatibility。
可空性注解处理的改进
Kotlin 支持通过可空性注解处理来自 Java 的类型可空性信息。Kotlin 1.5.0 为该特性引入了多项改进:
- 它会读取作为依赖使用的已编译 Java 库中类型实参上的可空性注解。
- 它支持具有
TYPE_USE目标的可空性注解,适用于: - 数组
- 可变参数
- 字段
- 类型参数及其上界
- 基类和接口的类型实参
- 如果某个可空性注解有多个适用于某个类型的目标,并且其中一个是
TYPE_USE,则优先使用TYPE_USE。例如,如果@Nullable同时支持TYPE_USE和METHOD目标,那么方法签名@Nullable String[] f()会变成fun f(): Array<String?>!。
对于这些新支持的情形,在从 Kotlin 调用 Java 时使用错误的类型可空性会产生警告。使用 -Xtype-enhancement-improvements-strict-mode 编译器选项可以对这些情形启用严格模式(报告错误)。
Kotlin/Native
Kotlin/Native 现在性能更好、也更稳定。值得注意的变更包括:
性能改进
在 1.5.0 中,Kotlin/Native 获得了一组性能改进,既加快了编译速度,也加快了执行速度。
编译器缓存现在在调试模式下支持 linuxX64(仅限 Linux 主机)和 iosArm64 目标。启用编译器缓存后,除第一次以外的绝大多数调试编译都完成得更快。测量显示,在我们的测试项目上速度提升约 200%。
要为新的目标使用编译器缓存,请在项目的 gradle.properties 中添加以下代码以选择启用:
- 对于
linuxX64:kotlin.native.cacheKind.linuxX64=static - 对于
iosArm64:kotlin.native.cacheKind.iosArm64=static
如果启用编译器缓存后遇到任何问题,请在我们的问题跟踪器 YouTrack 中报告。
其他改进加快了 Kotlin/Native 代码的执行速度:
- 平凡的属性访问器会被内联。
- 字符串字面量上的
trimIndent()会在编译期间求值。
停用内存泄漏检查器
Kotlin/Native 内置的内存泄漏检查器已默认禁用。
它最初是为内部使用而设计的,只能在有限的情况下(而非所有情况)发现泄漏。此外,后来发现它还存在可能导致应用崩溃的问题。因此我们决定关闭内存泄漏检查器。
在某些情况下(例如单元测试),内存泄漏检查器仍然有用。对于这些情况,你可以通过添加以下代码行来启用它:
| |
请注意,不建议在应用运行时启用该检查器。
Kotlin/JS
Kotlin/JS 在 1.5.0 中迎来演进性变更。我们继续推动 JS IR 编译器后端走向稳定,并发布其他更新:
升级到 webpack 5
Kotlin/JS Gradle 插件现在对浏览器目标使用 webpack 5 而不是 webpack 4。这是一次重大的 webpack 升级,带来了不兼容的变更。如果你使用自定义的 webpack 配置,请务必查看 webpack 5 发行说明。
进一步了解使用 webpack 打包 Kotlin/JS 项目。
IR 编译器的框架与库
警告: Kotlin/JS IR 编译器处于 Alpha 阶段。它可能会以不兼容的方式发生变化,并在将来需要手动迁移。我们欢迎你在 YouTrack 中提供反馈。
在开发 Kotlin/JS 编译器基于 IR 的后端的同时,我们鼓励并帮助库作者以 both 模式构建项目。这意味着他们可以为两种 Kotlin/JS 编译器生成制品,从而壮大新编译器的生态。
许多知名框架和库已经支持 IR 后端:KVision、fritz2、doodle 等。如果你在项目中使用它们,现在就已经可以用 IR 后端构建,并体验它带来的好处。
如果你在编写自己的库,请以 “both” 模式编译,这样你的使用者也能在新编译器中使用它。
Kotlin Multiplatform
在 Kotlin 1.5.0 中,为每个平台选择测试依赖已被简化,现在由 Gradle 插件自动完成。
多平台项目中现在可以使用获取字符类别的新 API。
标准库
标准库经历了大量变更和改进,从稳定实验性部分到添加新特性:
- 稳定的无符号整数类型
- 稳定的与区域无关的文本大小写 API
- 稳定的字符到整数转换 API
- 稳定的 Path API
- 向下取整除法与 mod 运算符
- Duration API 变更
- 获取字符类别的新 API 现已在多平台代码中可用
- 新的集合函数 firstNotNullOf()
- String?.toBoolean() 的严格版本
你可以在这篇博客文章中进一步了解标准库的变更。
视频:New Standard Library Features
稳定的无符号整数类型
UInt、ULong、UByte、UShort 无符号整数类型现在已进入 Stable。对这些类型的运算、区间和数列也是如此。无符号数组及对它们的运算仍处于 Beta。
稳定的与区域无关的文本大小写 API
此版本带来了新的与区域无关的文本大小写转换 API。它为对区域敏感的 toLowerCase()、toUpperCase()、capitalize() 和 decapitalize() API 函数提供了替代方案。新 API 帮助你避免因不同区域设置而导致的错误。
Kotlin 1.5.0 提供以下完全 Stable 的替代方案:
- 对于
String函数:
|早期版本|1.5.0 替代方案|
|String.toUpperCase()|String.uppercase()|
|String.toLowerCase()|String.lowercase()|
|String.capitalize()|String.replaceFirstChar { it.uppercase() }|
|String.decapitalize()|String.replaceFirstChar { it.lowercase() }|
- 对于
Char函数:
|早期版本|1.5.0 替代方案|
|Char.toUpperCase()|Char.uppercaseChar(): CharChar.uppercase(): String|
|Char.toLowerCase()|Char.lowercaseChar(): CharChar.lowercase(): String|
|Char.toTitleCase()|Char.titlecaseChar(): CharChar.titlecase(): String|
注意: 对于 Kotlin/JVM,还有带显式
Locale参数的uppercase()、lowercase()和titlecase()重载。
旧的 API 函数已被标记为弃用,并将在未来的版本中移除。
请在 KEEP 中查看文本处理函数变更的完整列表。
稳定的字符到整数转换 API
从 Kotlin 1.5.0 开始,新的字符到编码、字符到数字的转换函数已进入 Stable。这些函数取代了当前常常与类似的字符串到 Int 转换相混淆的 API 函数。
新 API 消除了这种命名混淆,使代码行为更透明、更明确。
此版本引入了 Char 转换,它们被划分为以下几组命名清晰的函数:
- 用于获取
Char的整数编码以及从给定编码构造Char的函数:
| |
- 用于把
Char转换为它所表示数字的数值的函数:
| |
- 一个
Int扩展函数,用于把它所表示的非负个位数转换为对应的Char表示:
| |
旧的转换 API 现在已被弃用,包括带各种实现的 Number.toChar()(除 Int.toChar() 之外的所有实现)以及用于转换为数值类型的 Char 扩展,例如 Char.toInt()。
稳定的 Path API
带有 java.nio.file.Path 扩展的实验性 Path API现在已进入 Stable。
| |
向下取整除法与 mod 运算符
标准库中新增了用于模运算的运算:
floorDiv()返回向下取整除法的结果。它可用于整数类型。mod()返回向下取整除法的余数(模)。它可用于所有数值类型。
这些运算看起来与现有的整数除法和 rem() 函数(或 % 运算符)非常相似,但它们对负数的行为不同:
a.floorDiv(b)与普通/的区别在于floorDiv把结果向下取整(朝向更小的整数),而/把结果截断为更接近 0 的整数。a.mod(b)是a与a.floorDiv(b) * b之间的差。它要么为零,要么与b同号,而a % b可能与其异号。
| |
Duration API 变更
警告: Duration API 是实验性的。它随时可能被放弃或更改。请仅将其用于评估目的。我们欢迎你在 YouTrack 中提供反馈。
有一个实验性的 Duration 类,用于以不同时间单位表示时长。在 1.5.0 中,Duration API 有以下变更:
- 内部值表示现在使用
Long而不是Double,以提供更好的精度。 - 新增了把
Long转换为特定时间单位的 API。它取代了旧的、以Double值运算的 API,后者现在已被弃用。例如,Duration.inWholeMinutes返回以Long表示的时长值,并取代Duration.inMinutes。 - 新增了从数字构造
Duration的伴生函数。例如,Duration.seconds(Int)创建一个表示整数秒数的Duration对象。像Int.seconds这样的旧扩展属性现在已被弃用。
| |
获取字符类别的新 API 现已在多平台代码中可用
Kotlin 1.5.0 引入了在多平台项目中按照 Unicode 获取字符类别的新 API。若干函数现在在所有平台和通用代码中都可用。
用于检查字符是字母还是数字的函数:
| |
用于检查字符大小写的函数:
| |
其他一些函数:
属性 Char.category 及其返回类型枚举类 CharCategory(用于表示字符按 Unicode 的通用类别)现在也可在多平台项目中使用。
新的集合函数 firstNotNullOf()
新的 firstNotNullOf() 和 firstNotNullOfOrNull() 函数把 mapNotNull() 与 first() 或 firstOrNull() 结合起来。它们用自定义的选择器函数映射原集合并返回第一个非 null 值。如果不存在这样的值,firstNotNullOf() 抛出异常,firstNotNullOfOrNull() 返回 null。
| |
String?.toBoolean() 的严格版本
两个新函数引入了现有的 String?.toBoolean() 的大小写敏感严格版本:
String.toBooleanStrict()对除字面量true和false之外的所有输入抛出异常。String.toBooleanStrictOrNull()对除字面量true和false之外的所有输入返回 null。
| |
kotlin-test 库
kotlin-test 库引入了一些新特性:
多平台项目中简化的测试依赖用法
现在你可以使用 kotlin-test 依赖为 commonTest 源集添加测试依赖,Gradle 插件会为每个测试源集推断相应的平台依赖:
- 对于 JVM 源集使用
kotlin-test-junit,请参见自动为 Kotlin/JVM 源集选择测试框架 - 对于 Kotlin/JS 源集使用
kotlin-test-js - 对于通用源集使用
kotlin-test-common和kotlin-test-annotations-common - 对于 Kotlin/Native 源集不需要额外的制品
此外,你可以在任何共享源集或平台特定源集中使用 kotlin-test 依赖。
现有的带显式依赖的 kotlin-test 配置在 Gradle 和 Maven 中都能继续工作。
进一步了解设置对测试库的依赖。
自动为 Kotlin/JVM 源集选择测试框架
Gradle 插件现在会自动选择并添加对测试框架的依赖。你只需在通用源集中添加 kotlin-test 依赖即可。
Gradle 默认使用 JUnit 4。因此 kotlin("test") 依赖会解析为 JUnit 4 对应的变体,即 kotlin-test-junit:
Kotlin
| |
Groovy
| |
你可以在 test 任务中调用 useJUnitPlatform() 或 useTestNG() 来选择 JUnit 5 或 TestNG:
| |
你可以在项目的 gradle.properties 中添加 kotlin.test.infer.jvm.variant=false 这一行来禁用自动选择测试框架。
进一步了解设置对测试库的依赖。
断言函数更新
此版本带来了新的断言函数,并改进了现有函数。
kotlin-test 库现在具有以下特性:
- 检查值的类型
你可以使用新的 assertIs<T> 和 assertIsNot<T> 检查值的类型:
| |
由于类型擦除,在下面的示例中该断言函数只检查 value 是否为 List 类型,并不检查它是否是元素类型为特定 String 的列表:assertIs<List<String>>(value)。
- 比较数组、序列和任意可迭代对象的内容
新增了一组重载的 assertContentEquals() 函数,用于比较不实现结构相等性的不同集合的内容:
| |
- 为
Double和Float数字新增的assertEquals()和assertNotEquals()重载
assertEquals() 函数新增了重载,使得可以按绝对精度比较两个 Double 或 Float 数字。精度值作为函数的第三个参数指定:
| |
- 用于检查集合和元素内容的新函数
你现在可以使用 assertContains() 函数检查集合或元素是否包含某个内容。它可以用于具有 contains() 运算符的 Kotlin 集合和元素,例如 IntRange、String 等:
| |
assertTrue()、assertFalse()、expect()函数现在是内联的
从现在起,你可以把它们用作内联函数,因此可以在 lambda 表达式内调用挂起函数:
| |
kotlinx 库
随 Kotlin 1.5.0 一起,我们发布了 kotlinx 库的新版本:
协程 1.5.0-RC
kotlinx.coroutines 1.5.0-RC 发布,带来:
从 Kotlin 1.5.0 开始,实验性协程已被禁用,-Xcoroutines=experimental 标志不再受支持。
请在变更日志和 kotlinx.coroutines 1.5.0 发布博客文章中了解更多。
序列化 1.2.1
kotlinx.serialization 1.2.1 发布,带来:
- JSON 序列化性能的改进
- JSON 序列化中支持多个名称
- 从
@Serializable类生成实验性的 .proto schema - 以及更多内容
请在变更日志和 kotlinx.serialization 1.2.1 发布博客文章中了解更多。
视频:kotlinx.serialization 1.2.1
dateTime 0.2.0
kotlinx-datetime 0.2.0 发布,带来:
@Serializable的 Datetime 对象- 规范化的
DateTimePeriod和DatePeriodAPI - 以及更多内容
请在变更日志和 kotlinx-datetime 0.2.0 发布博客文章中了解更多。
迁移到 Kotlin 1.5.0
一旦 1.5.0 可用,IntelliJ IDEA 和 Android Studio 就会建议把 Kotlin 插件更新到该版本。
要把现有项目迁移到 Kotlin 1.5.0,只需把 Kotlin 版本改为 1.5.0 并重新导入你的 Gradle 或 Maven 项目。了解如何更新到 Kotlin 1.5.0。
要使用 Kotlin 1.5.0 开始一个新项目,请更新 Kotlin 插件,然后从 File | New | Project 运行项目向导。
新的命令行编译器可在 GitHub 发布页面下载。
Kotlin 1.5.0 是一个特性版本,因此可能带来不兼容的语言变更。你可以在 Kotlin 1.5 兼容性指南中找到这些变更的详细列表。