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
可以通过 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 后,可以使用下面的替代函数:
旧的 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()
函数,它们结合了 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
编译器选项启用此特性。
以上是关于Kotlin 1.5新特性记录的主要内容,如果未能解决你的问题,请参考以下文章