13.6.10.1 Kotlin 1.4.30 的新变化

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

13.6.10.1 Kotlin 1.4.30 的新变化

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

发布日期:2021 年 2 月 3 日

Kotlin 1.4.30 提供了新语言特性的预览版本,把 Kotlin/JVM 编译器的新 IR 后端提升为 Beta,并带来了各种性能和功能改进。

你也可以在这篇博客文章中了解新特性。

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

语言特性

Kotlin 1.5.0 将带来新的语言特性 —— 支持 JVM record、密封接口以及稳定的内联类。在 Kotlin 1.4.30 中,你可以以预览模式试用这些特性和改进。如果你能在相应的 YouTrack 议题中与我们分享反馈,我们将非常感激,因为这让我们有机会在 1.5.0 发布之前处理它们。

要以预览模式启用这些语言特性和改进,你需要通过添加特定的编译器选项来选择启用。详见下文各节。

在这篇博客文章中进一步了解新特性预览。

JVM record 支持

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

JDK 16 版本计划稳定一种名为 record 的新 Java 类类型。为了提供 Kotlin 的所有优势并维持其与 Java 的互操作,Kotlin 正在引入对 record 类的实验性支持。

你可以像使用带属性的 Kotlin 类一样使用在 Java 中声明的 record 类,无需任何额外步骤。

从 1.4.30 开始,你可以使用 @JvmRecord 注解为 data 类在 Kotlin 中声明 record 类:

1
2
@JvmRecord
data class User(val name: String, val age: Int)

要试用 JVM record 的预览版本,请添加编译器选项 -Xjvm-enable-preview 和 -language-version 1.5。

我们仍在继续开发 JVM record 支持,如果你能通过这个 YouTrack 议题与我们分享反馈,我们将非常感激。

在 KEEP 中进一步了解其实现、限制和语法。

密封接口

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

在 Kotlin 1.4.30 中,我们提供了_密封接口_的原型。它们与密封类互补,让构建更灵活的受限类层次结构成为可能。

它们可以作为“内部”接口使用,无法在同一模块之外实现。例如,你可以依赖这一事实来编写穷尽的 when 表达式。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
sealed interface Polygon

class Rectangle(): Polygon
class Triangle(): Polygon

// when() 是穷尽的:不会再出现其他多边形实现
// 在模块编译之后
fun draw(polygon: Polygon) = when (polygon) {
    is Rectangle -> // ...
    is Triangle -> // ...
}

另一个用例:有了密封接口,你可以让一个类继承自两个或多个密封父类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
sealed interface Fillable {
   fun fill()
}
sealed interface Polygon {
   val vertices: List<Point>
}

class Rectangle(override val vertices: List<Point>): Fillable, Polygon {
   override fun fill() { /*...*/ }
}

要试用密封接口的预览版本,请添加编译器选项 -language-version 1.5。切换到该版本后,你就可以在接口上使用 sealed 修饰符。如果你能通过这个 YouTrack 议题与我们分享反馈,我们将非常感激。

进一步了解密封接口。

包级密封类层次结构

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

密封类现在可以组成更灵活的层次结构。它们可以在同一编译单元、同一包的所有文件中拥有子类。此前,所有子类都必须出现在同一个文件中。

直接子类可以是顶层的,也可以嵌套在任意数量的其他具名类、具名接口或具名对象内部。密封类的子类必须具有正确限定的名称 —— 它们不能是局部对象或匿名对象。

要试用包级密封类层次结构,请添加编译器选项 -language-version 1.5。如果你能通过这个 YouTrack 议题与我们分享反馈,我们将非常感激。

进一步了解包级密封类层次结构。

改进的内联类

警告: 内联值类处于 Beta 阶段。它们几乎已经稳定,但将来可能仍需要迁移步骤。我们会尽力把你需要做的改动降到最少。我们非常欢迎你在 YouTrack 上提供关于内联类特性的反馈。

Kotlin 1.4.30 把内联类提升为 Beta,并为它们带来以下特性和改进:

  • 由于内联类是基于值的,你可以使用 value 修饰符定义它们。inline 和 value 修饰符现在彼此等价。在未来的 Kotlin 版本中,我们计划弃用 inline 修饰符。

从现在开始,对于 JVM 后端,Kotlin 要求类声明之前带有 @JvmInline 注解:

1
2
3
4
5
6
7
  inline class Name(private val s: String)

  value class Name(private val s: String)

  // 用于 JVM 后端
  @JvmInline
  value class Name(private val s: String)
  • 内联类可以有 init 块。你可以添加在类实例化之后立即执行的代码:
1
2
3
4
5
6
  @JvmInline
  value class Negative(val x: Int) {
    init {
        require(x < 0) { }
    }
  }
  • 从 Java 代码调用带内联类的函数:在 Kotlin 1.4.30 之前,由于名称改写,你无法从 Java 调用接受内联类的函数。从现在开始,你可以手动禁用名称改写。要从 Java 代码调用这类函数,应在函数声明之前添加 @JvmName 注解:
1
2
3
4
5
6
  inline class UInt(val x: Int)

  fun compute(x: Int) { }

  @JvmName("computeUInt")
  fun compute(x: UInt) { }
  • 在本次发布中,我们修改了函数的名称改写方案以修复不正确的行为。这些改动导致了 ABI 变化。

从 1.4.30 开始,Kotlin 编译器默认使用新的名称改写方案。使用 -Xuse-14-inline-classes-mangling-scheme 编译器标志可以强制编译器使用旧的 1.4.0 改写方案并保持二进制兼容性。

Kotlin 1.4.30 把内联类提升为 Beta,我们计划在未来的版本中把它们变为稳定。如果你能通过这个 YouTrack 议题与我们分享反馈,我们将非常感激。

要试用内联类的预览版本,请添加编译器选项 -Xinline-classes 或 -language-version 1.5。

在 KEEP 中进一步了解名称改写算法。

进一步了解内联类。

Kotlin/JVM

JVM IR 编译器后端达到 Beta

面向 Kotlin/JVM 的基于 IR 的编译器后端在 1.4.0 中以 Alpha 形式推出,现在已达到 Beta。这是 IR 后端成为 Kotlin/JVM 编译器默认后端之前的最后一个预稳定级别。

我们现在取消了对消费 IR 编译器生成产物的限制。此前,只有在你启用了新后端的情况下,才能使用由新的 JVM IR 后端编译的代码。从 1.4.30 开始,不再有这样的限制,因此你可以使用新后端为第三方构建组件,例如库。试用新后端的 Beta 版本,并在我们的问题跟踪器中分享反馈。

要启用新的 JVM IR 后端,请在项目的配置文件中添加以下几行:

  • 在 Gradle 中:

Kotlin

1
2
3
  tasks.withType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile::class) {
    kotlinOptions.useIR = true
  }

Groovy

1
2
3
  tasks.withType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile) {
    kotlinOptions.useIR = true
  }
  • 在 Maven 中:
1
2
3
4
5
  <configuration>
      <args>
          <arg>-Xuse-ir</arg>
      </args>
  </configuration>

在这篇博客文章中进一步了解 JVM IR 后端带来的变化。

Kotlin/Native

性能改进

Kotlin/Native 在 1.4.30 中获得了各种性能改进,从而缩短了编译时间。例如,使用 Kotlin Multiplatform Mobile 进行网络与数据存储示例中重建 framework 所需的时间从 9.5 秒(1.4.10 中)降至 4.5 秒(1.4.30 中)。

Apple watchOS 64 位模拟器目标

自 7.0 版本起,watchOS 的 x86 模拟器目标已被弃用。为了跟上最新的 watchOS 版本,Kotlin/Native 提供了新目标 watchosX64,用于在 64 位架构上运行模拟器。

支持 Xcode 12.2 库

我们添加了对 Xcode 12.2 提供的新库的支持。现在你可以在 Kotlin 代码中使用它们。

Kotlin/JS

顶层属性的惰性初始化

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

Kotlin/JS 的 IR 后端正在获得顶层属性惰性初始化的原型实现。这减少了应用启动时需要初始化所有顶层属性的必要,应该能显著改善应用启动时间。

我们会继续改进惰性初始化,也请你试用当前的原型,并在这个 YouTrack 议题或官方 Kotlin Slack 的 #javascript 频道中分享你的想法和结果(在这里获取邀请)。

要使用惰性初始化,请在使用 JS IR 编译器编译代码时添加 -Xir-property-lazy-initialization 编译器选项。

Gradle 项目改进

支持 Gradle 配置缓存

从 1.4.30 开始,Kotlin Gradle 插件支持配置缓存特性。它会加快构建过程:一旦你运行命令,Gradle 就会执行配置阶段并计算任务图。Gradle 会缓存结果并在后续构建中复用。

要开始使用该特性,你可以使用 Gradle 命令或配置基于 IntelliJ 的 IDE。

标准库

与区域设置无关的大小写转换 API

警告: 与区域设置无关的 API 特性是实验性的。它可能随时被移除或更改。请仅将其用于评估目的。我们非常欢迎你在 YouTrack 上提供反馈。

本次发布引入了实验性的、与区域设置无关的 API,用于改变字符串和字符的大小写。当前的 toLowerCase()、toUpperCase()、capitalize()、decapitalize() API 函数是区分区域设置的。这意味着不同的平台区域设置可能影响代码行为。例如在土耳其语区域设置中,用 toUpperCase 转换字符串 “kotlin” 的结果是 “KOTLİN”,而不是 “KOTLIN”。

1
2
3
4
5
// 当前 API
println("Needs to be capitalized".toUpperCase()) // NEEDS TO BE CAPITALIZED

// 新 API
println("Needs to be capitalized".uppercase()) // NEEDS TO BE CAPITALIZED

Kotlin 1.4.30 提供了以下替代方案:

  • 对于 String 函数:

|早期版本|1.4.30 替代方案|

|String.toUpperCase()|String.uppercase()| |String.toLowerCase()|String.lowercase()| |String.capitalize()|String.replaceFirstChar { it.uppercase() }| |String.decapitalize()|String.replaceFirstChar { it.lowercase() }|

  • 对于 Char 函数:

|早期版本|1.4.30 替代方案|

|Char.toUpperCase()|Char.uppercaseChar(): Char
Char.uppercase(): String| |Char.toLowerCase()|Char.lowercaseChar(): Char
Char.lowercase(): String| |Char.toTitleCase()|Char.titlecaseChar(): Char
Char.titlecase(): String|

注意: 对于 Kotlin/JVM,还有带显式 Locale 参数的重载 uppercase()、lowercase() 和 titlecase() 函数。

在 KEEP 中查看文本处理函数变更的完整列表。

更清晰的 Char 与编码、Char 与数字之间的转换

警告: Char 转换的无歧义 API 特性是实验性的。它可能随时被移除或更改。请仅将其用于评估目的。我们非常欢迎你在 YouTrack 上提供反馈。

当前的 Char 到数字转换函数返回以不同数值类型表示的 UTF-16 编码值,它们经常与类似的字符串到 Int 转换(后者返回字符串的数值)混淆:

1
2
3
"4".toInt() // 返回 4
'4'.toInt() // 返回 52
// 而且此前没有能对 Char '4' 返回数值 4 的通用函数

为避免这种混淆,我们决定把 Char 转换分成以下两组名称清晰的函数:

  • 用于获取 Char 的整数编码以及从给定编码构造 Char 的函数:
1
2
3
  fun Char(code: Int): Char
  fun Char(code: UShort): Char
  val Char.code: Int
  • 用于把 Char 转换为其所表示数字的数值的函数:
1
2
  fun Char.digitToInt(radix: Int): Int
  fun Char.digitToIntOrNull(radix: Int): Int?
  • 一个 Int 的扩展函数,用于把它所表示的非负单个数字转换为对应的 Char 表示:
1
  fun Int.digitToChar(radix: Int): Char

更多细节请参阅 KEEP。

序列化更新

随着 Kotlin 1.4.30 一同发布的还有 kotlinx.serialization 1.1.0-RC,其中包含一些新特性:

  • 内联类的序列化支持
  • 无符号原始类型的序列化支持

内联类的序列化支持

从 Kotlin 1.4.30 开始,你可以让内联类可序列化:

1
2
@Serializable
inline class Color(val rgb: Int)

注意: 该特性需要新的 1.4.30 IR 编译器。

当可序列化的内联类在其他可序列化类中使用时,序列化框架不会对它们进行装箱。

在 kotlinx.serialization 文档中了解更多。

无符号原始类型的序列化支持

从 1.4.30 开始,你可以对无符号原始类型 UInt、ULong、UByte 和 UShort 使用 kotlinx.serialization 的标准 JSON 序列化器:

1
2
3
4
5
6
@Serializable
class Counter(val counted: UByte, val description: String)
fun main() {
   val counted = 239.toUByte()
   println(Json.encodeToString(Counter(counted, "tries")))
}

在 kotlinx.serialization 文档中了解更多。