Kotlin logo

The Kotlin Blog

Kotlin Programming Language by JetBrains

Kotlin 1.4.30 新的语言功能预览

我们计划在 Kotlin 1.5 中添加一些新的语言功能,但您现在就可以在 Kotlin 1.4.30 中试用这些功能:

要试用这些新功能,您需要 指定 1.5 语言版本

新的发布节奏意味着 Kotlin 1.5 将在几个月后推出,但新功能已经可以在 1.4.30 中预览。 您的早期反馈对我们至关重要,因此请立即尝试这些新功能!

内联 value 类的稳定化

自 Kotlin 1.3 开始,内联类即处于 Alpha 阶段,直到在 1.4.30 中被提升到 Beta 阶段。

Kotlin 1.5 稳定了内联类的概念,但使其成为更通用的特性 –value 类的一部分,我们将在后文介绍。

我们先来谈一谈内联类的工作原理。 如果您已经熟悉内联类,那么可以跳过本部分,直接进入新变化

首先,内联类消除了值周围的包装器:

内联类既可以是基元类型的包装器,也可以是任何引用类型的包装器,如 String

编译器在可能的情况下,会在字节码中用底层类型 (Int) 替换内联类实例(在我们的示例中为 Color 实例):

在后台,编译器生成名称经过修饰的 changeBackground 函数,它以 Int 为参数,直接传递 255 常量,而不在调用点创建包装器:

名称经过修饰,以允许采用不同内联类的实例的函数无缝重载,防止可能会违反内联类内部约束的 Java 代码的意外调用。 阅读下文,了解如何从 Java 中使其可用。
包装器并不总是在字节码中消除。 这仅在可能的情况下发生,并且与内置基元类型非常相似。 定义 Color 类型的变量或将其直接传递到函数时,它将被底层值替换:

在此示例中,color 变量在编译期间的类型为 Color,但在字节码中被替换为 Int

但是,如果您将其存储在集合中或传递到泛型函数,它将装箱到 Color 类型的常规对象中:

装箱和拆箱由编译器自动完成。 虽然您不必上手,但了解一下内部情况也是有所裨益的。

更改 Java 调用的 JVM 名称

从 1.4.30 开始,您可以使用内联类作为参数更改函数的 JVM 名称,使其可从 Java 使用。 此类名称默认经过修饰,以防止 Java 意外使用或冲突的重载(例如上例中的 changeBackground-euwHqFQ)。

如果使用 @JvmName 注解函数,它将在字节码中更改此函数的名称,使其可从 Java 调用并直接传递值:

与通常以 @JvmName 注解的函数一样,在 Kotlin 中,您可以通过其 Kotlin 名称进行调用。 Kotlin 用法是类型安全的,因为您只能将 Timeout 类型的值作为参数传递,并且单位在用法中可见。

您可以从 Java 直接传递 long 值。 它不再是类型安全的,也因此默认不工作。 对于代码中的 greetAfterTimeout(2),您很难立即看出是 2 秒、2 毫秒还是 2 年。

通过提供注解,您明确强调要从 Java 调用此函数。 描述性名称有助于避免混淆:在 JVM 名称中添加“Millis”后缀,让 Java 用户清楚了解这些单位。

Init blocks

1.4.30 中内联类的另一个改进是,您现在可以在 init 块中定义初始化逻辑:

这在过去是不被允许的。
您可以在相应的 KEEP文档和此问题下的讨论中阅读有关内联类的更多详细信息。

内联 value 类

Kotlin 1.5 稳定了内联类的概念,并使其成为更通用的特性 value 类的一部分。

“内联类”此前一直是一项单独的语言功能,但现在正成为针对含一个参数的 value 类的特定 JVM 优化。 value 类代表了一个更通用的概念,并且将支持不同的优化:现在是内联类,未来当 Valhalla 项目可用时将支持 Valhalla 基元类(下文将详细介绍)。

目前对您来说,唯一改变的是语法。 由于内联类是优化的 value 类,您必须使用与以往不同的方式声明它:

可以使用一个构造函数参数定义一个 value 类,并以 @JvmInline 注解。 我们希望所有人都可以从 Kotlin 1.5 开始使用这种新语法。 旧语法 inline class 将继续工作一段时间。 它将在 1.5 中被弃用,并显示警告,1.5 版本中将包含一个自动迁移所有声明的选项。 后续将变为带有错误的弃用。

Value 类

value 类表示具有数据的不可变实体。 目前,一个 value 类只能包含一个属性来支持“旧”内联类的用例。

在未来完全支持此功能的 Kotlin 版本中,将可以定义具有许多属性的 value 类。 所有值均应为只读 val

value 类没有身份:它们完全由存储的数据定义,并且不可接受 === 身份检查。 == 相等性检查会自动比较底层数据。

value 类的这种“无身份”特质为未来的重大优化奠定了基础:在 Valhalla 项目进入 JVM 后,将允许 value 类在后台作为 JVM 基元类实现。

不变性约束以及 Valhalla 优化的可能性使 value 类不同于 data 类。

未来 Valhalla 优化

Valhalla 项目 为 Java 和 JVM 引入了一个新概念:基元类。

基元类的主要目标是将基元的高性能与常规 JVM 类的对象导向优势相结合。 基元类是数据持有者,其实例可以存储在计算堆栈上的变量中,并且可以直接操作,没有头和指针。 在这方面,它们类似于 intlong 等基元值(在 Kotlin 中,您不直接使用基元类型,而是由编译器在后台生成)。

基元类的一个重要优点是,它们允许内存中对象的扁平和密集布局。 目前,Array<Point> 是引用的数组。 借助 Valhalla 支持,在将 Point 定义为基元类(Java 术语)或定义为具有底层优化的 value 类(Kotlin 术语)时,JVM 可以对其进行优化,并将 Point 的数组以“扁平”布局存储,直接作为由许多 xy 组成的数组,而不是作为引用数组。

我们非常期待即将到来的 JVM 变化,我们希望 Kotlin 能从中受益。 同时,我们不想强迫社区依赖新的 JVM 版本才能使用 value 类,所以我们也将为较早的 JVM 版本提供支持。 在将代码编译到支持 Valhalla 的 JVM 时,最新的 JVM 优化将适用于 value 类。

突变方法

关于 value 类的功能,还有许多值得介绍。 由于 value 类表示“不可变”数据,因此可以使用突变方法(例如 Swift 中的突变方法)。 突变方法是指成员函数或属性 setter 返回新实例而不是更新现有实例,主要好处是您可以采用熟悉的语法。 这仍然需要在语言中进行原型设计。

更多详细信息

@JvmInline 注解是特定于 JVM 的。 在其他后端,value 类可以由不同的方式实现。 例如 Kotlin/Native 中的 Swift 结构。

您可以在 Kotlin value 类的设计说明中阅读有关 value 类的详细信息,或观看 Roman Elizarov 的“展望未来”演讲节选。

支持 JVM 记录

JVM 生态系统中将迎来的另一项改进是 Java 记录。 它们类似于 Kotlin data 类,主要用作简单的数据持有者。

Java records don’t follow the JavaBeans convention, and they have x() and y() methods instead of the familiar getX() and getY().

与 Java 的互操作性一直以来都是并且将永远作为 Kotlin 的优先考虑事项。 因此,Kotlin 代码可以“理解”新的 Java 记录,并将其视为具有 Kotlin 属性的类。 这类似于遵循 JavaBeans 惯例的常规 Java 类:

主要出于互操作性,您可以用 @JvmRecord 注解 data 类,生成新的 JVM 记录方法:

@JvmRecord 注解使编译器生成 x()y() 方法,而不是标准的 getX()getY() 方法。 我们假设您只需要在将类从 Java 转换到 Kotlin 时使用此注解保存类的 API。 在所有其他用例中,都可以使用 Kotlin 常见的 data 类,而不会出现问题。

只有将 Kotlin 代码编译到 JVM 15 以上版本时,此注解才可用。 您可以在相应的 KEEP文档和此问题下的讨论中阅读有关此功能的更多详细信息。

密封接口和密封类的改进

使类密封时,它会将层次结构限制为已定义的子类,从而允许在 when 分支中进行详尽的检查。 在 Kotlin 1.4 中,密封类层次结构具有两个约束。 首先,顶层类不能是一个密封接口,应该是一个类。 其次,所有子类均应当位于同一文件中。

Kotlin 1.5 去除了这两个约束:您现在可以使一个接口密封。 子类(包括密封类和密封接口)应与 super 类位于同一编译单元和同一软件包中,但现在可以位于不同文件中。

密封类以及现在的密封接口对于定义抽象数据类型 (ADT) 层次结构非常实用。

使用密封接口现在还能够很好地解决了另一个重要使用场景,即关闭一个接口的库外继承与实现。 将接口定义为密封会将其实现限制到相同的编译单元和相同的软件包,对于库,这样可以禁止库外实现。

例如,kotlinx.coroutines 软件包中的 Job 接口只能在 kotlinx.coroutines 库中实现。 sealed 后,此意图会变成显式:

作为库的用户,您将无法再定义自己的 Job 子类。 这过去一直是“隐含的”,但是使用密封接口后,编译器可以正式禁止。

未来使用 JVM 支持

Java 15 和 JVM 中引入了对密封类的预览支持。 未来,如果您将 Kotlin 代码编译到最新的 JVM(很可能到 JVM 17 或更高版本时此功能会稳定提供),我们将对密封类使用自然的 JVM 支持。

在 Java 中,显式列出给定密封类或接口的所有子类:

此信息使用新的 PermittedSubclasses 特性存储在类文件中。 JVM 可在运行时识别密封类,并防止未授权子类的扩展。

未来,当您将 Kotlin 编译到最新的 JVM 时,将使用这种新的 JVM 对密封类的支持。 在后台,编译器将在字节码中生成允许的子类列表,确保存在 JVM 支持和额外运行时检查。

在 Kotlin 中,您无需指定子类列表! 编译器将根据同一软件包中的声明子类生成列表。

super 类或接口的子类的显式指定功能可能会在以后作为可选规范添加。 目前我们认为没有必要,但我们很乐意了解您的用例,以及您是否需要这个功能!

请注意,对于较早的 JVM 版本,理论上可以为 Kotlin 密封接口定义 Java 子类,但不要这么做! 由于 JVM 对允许的子类的支持尚不可用,所以此约束只能由 Kotlin 编译器执行。 我们将添加 IDE 警告,防止意外执行此操作。 未来,新机制将用于最新的 JVM 版本,确保没有来自 Java 的“未授权”子类。

您可以在相应的 KEEP文档和此问题下讨论中阅读有关密封接口和放宽密封类限制的更多详细信息。

如何试用新功能

您需要使用 Kotlin 1.4.30。 指定语言版本 1.5 以启用新功能:

要试用 JVM 记录,还需要使用 jvmTarget 15 并启用 JVM 预览功能:添加编译器选项 -language-version 1.5-Xjvm-enable-preview

预发布说明

请注意,对新功能的支持仍为实验性质,并且 1.5 语言版本的支持处于预发布状态。 在 Kotlin 1.4.30 编译器中将语言版本设置为 1.5 相当于试用 1.5-M0 预览版。 后向兼容性保证不涵盖预发布版本。 功能和 API 在后续版本中可能发生变化。 在我们发布最终 Kotlin 1.5-RC 时,预发布版本产生的所有二进制文件都会被编译器禁止,您需要重新编译通过 1.5‑Mx 编译的所有内容。

分享您的反馈

请注意,对新功能的支持仍为实验性质,并且 1.5 语言版本的支持处于预发布状态。 在 Kotlin 1.4.30 编译器中将语言版本设置为 1.5 相当于试用 1.5-M0 预览版。 后向兼容性保证不涵盖预发布版本。 功能和 API 在后续版本中可能发生变化。 在我们发布最终 Kotlin 1.5-RC 时,预发布版本产生的所有二进制文件都会被编译器禁止,您需要重新编译通过 1.5‑Mx 编译的所有内容。

延伸阅读和讨论:

Discover more