Kotlin 1.5新特性记录

Posted RikkaTheWorld

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin 1.5新特性记录相关的知识,希望对你有一定的参考价值。

所有内容均来源于官网,会过滤掉非 Kotlin / JVM 的平台特性。


文章目录

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 Polygon

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

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

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

class FilledRectangle: Polygon, Fillable

更多请看: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

可以通过 GradleMavencommand-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_USEMETHOD 作为目标,方法签名 @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

稳定的无符号整数类型

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

Learn more about unsigned integer types

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

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

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



旧的 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: Int
  • 将 Char 转换为它所代表的数字值:
fun Char.digitToInt(radix: Int): Int
fun Char.digitToIntOrNull(radix: Int): Int?
  • Int 的扩展函数,用于将其表示的非负整数转换为相应的 Char
fun Int.digitToChar(radix: Int): Char

旧的转换 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() 函数,它们结合了 mapNotNullfist()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编译器选项启用此特性。

以上是关于Kotlin 1.5新特性记录的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin 1.5新特性记录

Kotlin 1.5 新特性 Inline classes,还不了解一下?

Kotlin 1.5 新特性:密封接口有啥用?

JDK10新特性

官方文档Kotlin 1.2 的新增特性

Kotlin 1.8.0 现已发布,有那些新特性?