0
点赞
收藏
分享

微信扫一扫

Kotlin 1.5新特性记录



文章目录

  • ​​1 1.5.0 新特性​​
  • ​​1.1 语言特性​​
  • ​​支持 JVM 记录类​​
  • ​​Sealed 接口​​
  • ​​内联类​​
  • ​​1.2 Kotlin / JVM​​
  • ​​稳定的基于IR的 JVM 编译器​​
  • ​​新的默认 Jvm 构建版本 是1.8​​
  • ​​通过 invokedynamic 来调用 SAM​​
  • ​​使用 invokedynamic 调用 lmabda 表达式​​
  • ​​@JvmDefault 和旧的 Xjvm-default 已弃用​​
  • ​​改进了可空性注解​​
  • ​​1.3 标准库 stdlib​​
  • ​​稳定的无符号整数类型​​
  • ​​稳定的区域无关API,用于转换大小写文本​​
  • ​​稳定的字符转整数API​​
  • ​​稳定的 Path Api​​
  • ​​`Duration` Api 更改​​
  • ​​新的集合函数 firstNotNullOf​​
  • ​​String?.toBoolean() 函数更加严格​​
  • ​​2 1.5.20 新特性​​
  • ​​2.1 Kotlin /JVM​​
  • ​​通过 invokedynamic 连接字符串​​
  • ​​支持 JSpecify 空注释​​
  • ​​3 1.5.30 新特性​​
  • ​​3.1 语言特性​​
  • ​​使 sealed 和 Boolean 的 when 流程控制更加详尽​​
  • ​​挂起函数作为超类型​​
  • ​​要求选择性加入实验性 API 的隐式用法​​
  • ​​改进了推断递归泛型类型​​
  • ​​消除构建器推断限制​​

1 1.5.0 新特性

Kotlin 1.5.0 的新增项:

  • 新的语言特性
  • 稳定的基于IR的 JVM 编译器后台
  • 性能提高
  • 提供对特性的渐进式更改,例如稳定实验性特性,和淘汰过时的特性

1.1 语言特性

支持 JVM 记录类

Java 正在快速的发展,为了保证 Kotlin 和它的互操作性,我们引入了 JVM 最新的一个新特性 - ​​record classes(记录类)​​。

Kotlin 对 JVM 记录类的支持包括双向互操作性:

  • 在 Kotlin 代码中,可以直接 Java 的记录类,就像其它类那样
  • 如果需要在 Java 中使用 Kotlin 记录类,请使这个类作为一个数据类型,并使用​​@JvmRecord​​ 注解修饰它

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

更多请看: ​​Learn more about using JVM records in Kotlin​​

Sealed 接口

Kotlin 的接口现在可以使用 ​​selaled​​ 修饰符,它对接口的工作方式与对类的工作方式相同:密封接口的所有实现在编译时都是已知的。

sealed interface

例如,你可以根据这一特性来写 ​​when​​:

fun draw(polygon: Polygon) = when (polygon) {
is Rectangle -> // ...
is Triangle -> // …
// 不必在写 else 分支 ,因为所有的可能的实现都已覆盖
}

此外,在之前,密封只能由继承实现,而现在,密封接口可以更加灵活的支持那些受到限制的层次结构,因为一个类可以直接实现多个密封接口:

class FilledRectangle: Polygon,

更多请看:​​Learn more about sealed interfaces​​

内联类

内联类是基于值的子集合,这些类只用于保存值。你可以将它们看做一个值的包装类,并且你不用担心因为包装新类而带来的额外内存开销。这个特性在1.4就已经有了实验性 api,这一点可以看:​​使用内联类​​。

内联类可以使用 ​​value​​ 修饰符来声明:

value class Password(val s: String)

在 JVM 上,字段还需要一个特殊的 ​​@JvmInlin​​ 来修饰:

@JvmInline
value class Password(val s: String)

​inline​​ 修饰符现在已经弃用。

​​Learn more about inline classes.​​

1.2 Kotlin / JVM

Kotlin / JVM 得到了许多改进,包括内部的和面向用户的。下面是几个最值得注意的几个。

稳定的基于IR的 JVM 编译器

基于 IR 的 Kotlin / JVM 的编译器现在已经是稳定的了,并且在默认情况下会启用它。

从 Kotlin 1.4.0 开始,基于 ir 的编译器早期版本便已经开始是 preview 状态(可以手动使用),现在它已经成为了
1.5 的默认特性。 在较早的 Koltin 版本中,默认仍然使用早期的编译器版本。

你可以在这篇Blog中找到更多关于基于 IR 支持的好处,以及未来发展的方向:​​Blog 原文​​。

如果你需要在 Kotlin1.5.0 版本使用旧的编译支持,你可以在配置文件中添加下面的代码:

  • gradle 中

tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile> {
kotlinOptions.useOldBackend = true
}

  • maven 中

<configuration>
<args>
<arg>-Xuse-old-backend</arg>
</args>
</configuration>

新的默认 Jvm 构建版本 是1.8

现在 Kotlin / Jvm 编译的默认构建版本是 1.8,1.6版本被标注为已废弃。

如果你需要用 JVM 1.6 版本来构建,你仍然可以切到这个版本去。

通过 invokedynamic 来调用 SAM

Kotlin 1.5.0 现在使用动态调用 (​​invokedynamic​​) 来编译 SAM(单一抽象方法)转换,它可以涵盖:

  • 任意 Java 接口的 SAM 类型
  • 任意是 Kotlin 函数式接口的 lamdba 表达式

新的实现使用 ​​LambdaMetafactory.metafactory()​​ 方法,并且在编译期间不再生成辅助包装类。这减小了应用程序 Jar 包的大小,从而提高了 JVM 的启动性能。

如果要回滚到以前那种编译成匿名类的旧实现,可以添加下面选项:

-Xsam-conversions=class

可以通过 ​​Gradle​​​、​​Maven​​​、​​command-line compiler​​ 几篇文章进行学习。

使用 invokedynamic 调用 lmabda 表达式

将普通的 Koltin Lambda 编译为 invokedynamic 是实验性的,这种特性可以在任意时候被删除或更改。你应该只将其用于预研一些需求

Kotlin 1.5.0 引入了对将普通的 Kotlin lambda(未转换为函数接口的实例)编译为动态调用(invokedynamic)的实验性支持。该实现使用 ​​LambdaMetafactory.metafactory()​​ 生成更轻的二进制文件,它在运行时有效地生成必要的类。目前,与普通的 lambda 编译相比,它有三个限制:

  • 编译成 invokedynamic 的 lambda 是不可序列化的
  • 对这样的 lambda 调用​​toString​​ 函数可能会产生可读性较差的内容
  • 实验性的反射 API 不支持那些​​LambdaMetafactory​​ 创建的 lamdba 表达式

@JvmDefault 和旧的 Xjvm-default 已弃用

在 Kotlin 1.4.0 之前,有 ​​@JvmDefault​​​ 注解, 以及可以设置 ​​-Xjvm-default=enable​​​、​​-Xjvm-default=compatibility​​。它们用于为 Kotlin 接口中所有特定的非抽象成员创建 JVM 默认方法。

在 Kotlin1.4.0中,我们引入了新的方式: ​​官方文档​​,它为整个项目默认打开了这种特性。

在 Kotlin 1.5.0 中,我们已经弃用了 ​​@JvmDefault​​​注解和 ​​-Xjvm-default=enable​​​、​​-Xjvm-default=compatibility​​。

​​Learn more about default methods in the Java interop​​

改进了可空性注解

Kotlin 支持使用可空性注解,来处理来自 Java 的可空属性。Kotlin 1.5.0 对这种特性做了下面的改进:

  • 它可以在编译后的 Java 库中读取类型参数上的可空性注释,这些注解将用作依赖项(传递可空性注解)
  • 它支持以​​TYPE_USE​​ 注解为目标,进行可空注解:
  • Arrays
  • Varargs
  • Fields
  • 类型参数(泛型)及其边界
  • 基类和接口的类型参数
  • 如果可空性注释有多个适用于某个类型的目标,并且其中一个目标是​​TYPE_USE​​​,那么首选​​TYPE_USE​​​。例如,如果​​@Nullable​​​ 同时支持​​TYPE_USE​​​ 和​​METHOD​​​ 作为目标,方法签名​​@Nullable String[] f()​​​ 会变成​​fun f(): Array<String?>!​

对于这些新支持的情况,在从Kotlin调用Java时,使用错误的可空性类型会产生警告。使用 ​​-xtype-enhance-improvement -strict-mode​​ 编译器选项来去掉这些警告。

​​Learn more about null-safety and platform types.​​

1.3 标准库 stdlib

稳定的无符号整数类型

​UInt​​​、​​ULong​​​、​​UByte​​​、​​UShort​​ 这些无符号的数字现在都是稳定的了。对这些类型、范围及其递增的操作也是如此,无符号数组及其操作仍保留在 Beta 中。

​​Learn more about unsigned integer types​​

稳定的区域无关API,用于转换大小写文本

这个版本带来一个新的与区域无关的 API,用于大/小写文本转换。它提供了以前 ​​toLowerCase​​​、 ​​toUpperCase​​​、​​capitalize()​​​ 和 ​​decapitalize()​​ 这些函数的替代方法,这些 API 函数对语言环境更敏感,可以帮助你避免由于不同的地区设置而产生的错误。

从 Kotlin 1.5 后,可以使用下面的替代函数:

Kotlin 1.5新特性记录_java


Kotlin 1.5新特性记录_jvm_02


旧的 API 函数被标记为弃用,并在将来的版本中删除。

可以参考 ​​Git文档​​来查看更加完整的函数列表。

稳定的字符转整数API

从 Kotlin 1.5.0 开始,char-to-code 和 char-to-digit 转化的函数已经是稳定的了。这些函数取代了当前的 API 函数,而当前的 API 函数常常与 string-to-Int 转换相混淆。

新的 API 消除了这种命名歧义,使代码的行为更加透明和明确。

这个版本引入了 Char 类型的转换,这些转换被划分为以下几个明确命名的函数集:

  • 根据给定的整型构造出对应的​​Char​

fun Char(code: Int): Char
fun Char(code: UShort): Char
val Char.code:

  • 将 Char 转换为它所代表的数字值:

fun Char.digitToInt(radix: Int): Int
fun Char.digitToIntOrNull(radix: Int): Int?

  • ​Int​​​ 的扩展函数,用于将其表示的非负整数转换为相应的​​Char​

fun Int.digitToChar(radix: Int):

旧的转换 api,包括 ​​Number.toChar​​​ ,及其实现(除了 ​​Int.toChar​​​)和 ​​Char​​​ 用于转化成数字的扩展函数,如 ​​Char.toInt​​​ , 现在均已废弃。
​​​Learn more about the char-to-integer conversion API in KEEP.​​

稳定的 Path Api

带有 java.nio.file.Path 扩展的实验性 API 现在已经是稳定的了。

// 可以通过 “/” 来构造路径
val baseDir = Path("/base")
val subDir = baseDir / "subdirectory"

// 列出目录中的文件
val kotlinFiles: List<Path> = Path("/home/user").listDirectoryEntries("*.kt")

​​Learn more about the Path API.​​

Duration Api 更改

​Duration​​​ 是一个实验性的Api,用于表示持续的时间,并有不同的单位支持。在1.5.0, ​​Duration​​ api做出了如下改变:

  • 内部使用的值从​​Double​​​ 替换成​​Long​​,以提供更好的精度
  • 新增了 API 用于从​​Long​​​ 转化到特定的时间单位,它将取代旧的 Api,旧的 Api 使用​​Double​​​ 类型进行操作,现在已经弃用。 例如:​​Duration.inWholeMinutes​​​ 返回​​Long​​​ 类型的值, 替换了原来的​​Duration.inMinutes​​ 函数
  • 新增了一些伴生函数用于通过数字来构造​​Duration​​​,例如,​​Duration.secondes(Int)​​​ 创建一个​​Duration​​​ 独享,表示一个秒数整型。 旧的扩展属性,如​​Int.seconds​​ 现在已弃用

val duration = Duration.milliseconds(120000)
println("There are ${duration.inWholeSeconds} seconds in ${duration.inWholeMinutes} minutes")

新的集合函数 firstNotNullOf

新增了集合处理函数 ​​firstNotNullOf()​​​ 和 ​​firstNotNullOfOrNull()​​​ 函数,它们结合了 ​​mapNotNull​​​ 和 ​​fist()​​​ 或 ​​firstOrNull()​

通过传入自定义映射函数,并返回第一个非空值。 如果没有,可以根据情况抛异常或者返回null。

val data = listOf("Kotlin", "1.5")
println(data.firstNotNullOf(String::toDoubleOrNull))
println(data.firstNotNullOfOrNull(String::toIntOrNull))

// 1.5
// null

String?.toBoolean() 函数更加严格

对于已经有的 ​​String?.toBoolean()​​ 函数,引入了区分大小写的两个严格版本:

  • ​String.toBooleanStrict()​​, 除了 “true” / “false”,其他的字符串输入将会抛出异常
  • ​String.toBooleanStrictOrNull​​,除了 “true” / “false”,其他的字符串输入将会返回 null

println("true".toBooleanStrict())  // true
println("1".toBooleanStrictOrNull()) // null
// println("1".toBooleanStrict()) // Exception

2 1.5.20 新特性

2.1 Kotlin /JVM

通过 invokedynamic 连接字符串

Kotlin 1.5.20 在设置 JVM 编译版本 9+ 后,将字符串连接编译为动态调用(invokedynamic),从而与现代 Java 版本保持一致。更准确的说,它使用 ​​StringConcatFactory.makeConcatWithConstants()​​ 进行字符串连接。

如果要使用以前的 ​​StringBuilder.append()​​​ 连接,请添加编译器选项 ​​-XString-concat=inline​​。

支持 JSpecify 空注释

Kotlin 编译器可以读取各种类型的可空注解,从而将可空信息从 Java 传递给 Kotlin。 版本 1.5.20 引入了对 JSpecify 项目的支持,该项目包括一组标准的 Java 空注解。

使用 JSpecify, 你可以提供更详细的可空性信息,以帮助 Kotlin 保持与 Java 的空安全互操作。你可以为声明、包或模块范围设置默认的可空性,指定参数可空性等等。你可以在 ​​JSpecify 用户指南​​ 中找到有关这方面的更多细节。

下面是 Kotlin 如何处理 JSpecify 注释的示例:

// JavaClass.java
import org.jspecify.nullness.*;

@NullMarked
public class JavaClass {
public String notNullableString() { return ""; }
public @Nullable String nullableString() { return ""; }
}

// Test.kt
fun kotlinFun() = with(JavaClass()) {
notNullableString().length // OK
nullableString().length // Warning: receiver nullability mismatch
}

在 1.5.20 中,根据 JSpecify 提供的空性信息,所有可空性不匹配都会被警告,在使用 JSpecify 时没使用 ​​-Xjspecify-annotations=strict​​​ 和 ​​-Xtype-enhancement-improvements-strict-mode​​ 编译器选项来启用严格模式(带有错误报告)。请注意, JSpecify 项目正在积极开发中,它的 API 和实现可以在任何时候发生重大变化。

3 1.5.30 新特性

3.1 语言特性

使 sealed 和 Boolean 的 when 流程控制更加详尽

一个详尽的 ​​when​​​ 状态语句应当涵盖所有可能的分支情况,并再加上一条 ​​else​​ 分支,也就是说 when 要包含所有可能的情况。

我们将计划禁止非穷举 ​​when​​​ 状态语句的情况,使得其行为与 when 表达式一致。
为了能够顾确保迁移,可以配置编译器,使其在使用密封类或布尔值的语句时报告关于非穷举的错误。在 Kotlin1.6 中,默认情况下会出现这样的警告,以后将会变成错误。

sealed class Mode {
object ON : Mode()
object OFF : Mode()
}

fun main() {
val x: Mode = Mode.ON
when (x) {
Mode.ON -> println("ON")
}
// WARNING: Non exhaustive 'when' statements on sealed classes/interfaces
// will be prohibited in 1.7, add an 'OFF' or 'else' branch instead

val y: Boolean = true
when (y) {
true -> println("true")
}
// WARNING: Non exhaustive 'when' statements on Booleans will be prohibited
// in 1.7, add a 'false' or 'else' branch instead
}

要在 Kotlin1.5.30中启用此特性,请使用 Kotlin 1.6 版本,你还可以通过启用 ​​progressive mode​​模式将将警告改成错误。

kotlin {
sourceSets.all {
languageSettings.apply {
languageVersion = "1.6"
//progressiveMode = true // false by default
}
}
}

挂起函数作为超类型

Kotlin 1.5.30 上可以让挂起函数作为超类型,但有一些限制:

class MyClass: suspend () -> Unit {
override suspend fun invoke() { TODO() }
}

使用 ​​-language-version 1.6​​ 编译器选项来启用该特性:

kotlin {
sourceSets.all {
languageSettings.apply {
languageVersion = "1.6"
}
}
}

该特性具有以下限制条件:

  • 不能将普通函数和挂起函数类型混合作为超类型。这是由于 JVM 底层实现中挂起函数的实现细节造成的。它们在其中表示为带有标记接口的普通函数类型。由于使用了标记 (marker)接口,所以无法区分哪些超接口挂起了,哪些是普通。
  • 不能使用多个挂起函数超类型,如果有类型检查,你也不能使用多个普通函数超类型。

要求选择性加入实验性 API 的隐式用法

库的作者可以将一个实验性Api比较为 ​​opt-in​​ 来告知用户当前 api 的稳定状态。当使用 API 时,编译器会引发警告或错误。并且需要显式同意才能禁止使用。

在 Kotlin 1.5.30 中,编译器将签名中具有实验类型的任何声明视为实验类型。也就是说,即使是实验性 API 的隐式使用额,它也需要选择加入。例如,如果函数的返回类型标记为实验 API 元素,那么即使声明没有显式地标记为需要选择,函数的使用也需要选择。

// Library code

@RequiresOptIn(message = "This API is experimental.")
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS)
annotation class MyDateTime // Opt-in requirement annotation

@MyDateTime
class DateProvider // A class requiring opt-in

// Client code

// Warning: experimental API usage
fun createDateSource(): DateProvider { /* ... */ }

fun getDate(): Date {
val dateSource = createDateSource() // Also warning: experimental API usage
// ...
}

改进了推断递归泛型类型

在 Kotlin 和 Java 中,可以定义递归泛型类型,它在类型参数中引用自己。在 Kotlin 1.5.30 中,如果是递归泛型, Kotlin 编译器可以仅根据对应的类型参数的上界来推断类型参数。这使得使用递归泛型类型创建各种模式成为可能,这些模式在 Java 中经常用于创建构造者 Api。

// Kotlin 1.5.20
val containerA = PostgreSQLContainer<Nothing>(DockerImageName.parse("postgres:13-alpine")).apply {
withDatabaseName("db")
withUsername("user")
withPassword("password")
withInitScript("sql/schema.sql")
}

// Kotlin 1.5.30
val containerB = PostgreSQLContainer(DockerImageName.parse("postgres:13-alpine"))
.withDatabaseName("db")
.withUsername("user")
.withPassword("password")
.withInitScript("sql/schema.sql")

你可以通过传入 ​​-Xself-upper-bound-inference​​​ 或 ​​-language-version 1.6​​ 编译器选项来启用这些改进。

消除构建器推断限制

构建器(Builder)推断是一种特殊的类型推断,它允许你根据来自lambda参数内部其他调用的类型信息推断调用的类型参数。这在调用通用构建函数(如 ​​buildList()​​​ 或 ​​sequence()​​​)时很有用: ​​buildList {add("string")}​​ 。

在这样的lambda参数中,以前对于使用构建器推断试图推断的类型信息有一个限制。这意味着您只能指定它,而不能获取它。例如,如果没有显式指定类型参数,就不能在 ​​buildList()​​ 的lambda参数中调用get()。

Kotlin 1.5.30通过 ​​-Xunrestricted-builder-inference​​编译器选项消除了这些限制。添加此选项以启用之前禁止在泛型构建函数的lambda参数内调用:

@kotlin.ExperimentalStdlibApi
val list = buildList {
add("a")
add("b")
set(1, null)
val x = get(1)
if (x != null) {
removeAt(1)
}
}

@kotlin.ExperimentalStdlibApi
val map = buildMap {
put("a", 1)
put("b", 1.1)
put("c", 2f)
}

此外,您还可以使用 ​​-language-version 1.6​​编译器选项启用此特性。


举报

相关推荐

0 条评论