Kotlin|Kotlin 1.6 正式发布,都有哪些新特性()
文章图片
11月16日,Kotlin 1.6 正式对外发布。接下来就一起看一下在这个版本中都有哪些新的语法特性
1. 更安全的 when 语句 Kotlin 的 when 关键字允许我们在 case 分支中写表达式或者语句。1.6 之前在 case 分支写语句时存在安全隐患:
- 更安全的when语句(exhaustive when statements)
- 挂起函数类型可作父类 (suspending functions as supertypes )
- 普通函数转挂起函数(suspend conversion)
- Builder函数更加易用
- 递归泛型的类型推导
- 注解相关的一些优化
// 定义枚举
enum class Mode{
ON, OFF }
val x: Mode = Mode.ON// when表达式
val result = when(x) {Mode.ON -> 1 // case 中是一个表达式
Mode.OFF -> 2
}// when语句
when(x) {Mode.ON -> println("ON") // case 是一个语句
Mode.OFF -> println("OFF")
}
下表说明了编译器针对 when 关键字的检查内容
|x 的类型| 枚举、密封类/接口、Bool型等
(可穷举类型) | 不可穷举类型 |
---|---|
when表达式 | case 必须穷举所有分支,或者添加 else,否则编译出错 |
when语句 | case 可以不穷举所有分支,不会报错 |
when表达式
的检查比较严谨,如果 case
不能穷举所有分支或者缺少 else
,编译器会报错如下:ERROR: 'when' expression must be exhaustive, add necessary 'is TextMessage' branch or 'else' branch instead
但编译器对于
when语句
的检查却不够严谨,即使没有穷举所有分支也不会报错,不利于开发者写出安全的代码:// when语句
when(x) {
// WARNING: [NON_EXHAUSTIVE_WHEN] 'when' expression on enum is recommended to be exhaustive, add 'OFF' branch or 'else' branch instead
Mode.ON -> println("ON") // case 是一个语句
}
Kotlin 1.6 起,当你在
When语句
中是可穷举类型时必须处理所有分支,不能遗漏。考虑到历史代码可能很多,为了更平稳的过渡,1.6 对 when语句
中没有穷举的 case
会首先给出 Warning
,从 1.7 开始 Warning 将变为 Error 要求开发者强制解决。2. 挂起函数类型可作父类 Kotlin 中一个函数类型可以作为父类被继承。
class MyFun(var param: P): () -> Result {override fun invoke(): Result {// 基于成员 param 自定义逻辑
}
}fun handle(handler: () -> Result) {//...
}
Kotlin 代码中大量使用各种函数类型,许多方法都以函数类型作为参数。当你需要调用这些方法时,需要传入一个函数类型的实例。而当你想在实例中封装一些可复用的逻辑时,可以使用函数类型作为父类创建子类。
但是这种做法目前不适用于挂起函数,你无法继承一个
suspend
函数类型的父类class C : suspend () -> Unit {
// Error: Suspend function type is not allowed as supertypes
}C().startCoroutine(completion = object : Continuation {override val context: CoroutineContext
get() = TODO("Not yet implemented")override fun resumeWith(result: Result) {TODO("Not yet implemented")
}
})
但是以挂起函数作为参数或者 recevier 的方法还挺多的,所以 Kotlin 1.5.30 在 Preveiw 中引入了此 feature,这次 1.6 将其 Stable。
class MyClickAction : suspend () -> Unit {override suspend fun invoke() {
TODO() }
}fun launchOnClick(action: suspend () -> Unit) {
}
如上,你可以现在可以像这样调用了
launchOnClick(MyClickAction())
。需要注意普通函数类型作为父类是可以多继承的
class MyClickAction :() -> Unit, (View) -> Unit {override fun invoke() {TODO("Not yet implemented")
}override fun invoke(p1: View) {TODO("Not yet implemented")
}
}
但是目前挂起函数作为父类不支持多继承,父类列表中,既不能出现多个 suspend 函数类型,也不能有普通函数类型和suspend函数类型共存。
3. 普通函数转挂起函数 这个 feature 也是与函数类型有关。
Kotlin 中为一个普通函数添加 suspend 是无害的,虽然编译器会提示你没必要这么做。当一个函数签名有一个 suspend 函数类型参数,但是也允许你传入一个普通函数,在某些场景下是非常方便的。
//combine 的 transform 参数是一个 suspend 函数
public fun combine(
flow: Flow, flow2: Flow,
transform: suspend (a: T1, b: T2) -> R): Flow
= flow.combine(flow2, transform)suspend fun before4_1() {combine(
flowA, flowB
) {
a, b ->
a to b
}.collect {
(a: Int, b: Int) ->
println("$a and $b")
}
}
如上述代码所示,
flow
的 combine
方法其参数 transform
类型是一个 suspend
函数,我们希望再次完成一个 Pair
的创建。这个简单的逻辑本无需使用 suspend
,但在 1.4 之前只能像上面这样写。Kotlin 1.4 开始,普通函数的引用可以作为
suspend
函数传参,所以 1.4 之后可以改成下面的写法,代码更简洁:suspend fun from1_4() {combine(
flowA, flowB, ::Pair
).collect {
(a: Int, b: Int) ->
println("$a and $b")
}
}
1.4 之后仍然有一些场景中,普通函数不能直接转换为 suspend 函数使用
fun getSuspending(suspending: suspend () -> Unit) {}fun suspending() {}fun test(regular: () -> Unit) {
getSuspending { }// OK
getSuspending(::suspending) // OK from 1.4
getSuspending(regular)// NG before 1.6
}
比如上面
getSuspending(regular)
会报错如下:ERROR:The feature "suspend conversion" is disabled
Kotlin 1.6 起,所有场景的普通函数类型都可以自动转换为 suspend 函数传参使用,不会再看到上述错误。
4. Builder 函数更加易用 我们在构建集合时会使用一些
Builder函数
,比如 buildList
,buildMap
之类。@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun buildList(@BuilderInference builderAction: MutableList.() -> Unit): List {contract {
callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
return buildListInternal(builderAction)
}@kotlin.ExperimentalStdlibApi
val list = buildList> {add("a")
add("b")
}
buildList
的实现中使用 @BuilderInterface
注解了 builderAction
这个 lambda 。这样可以在调用时 buildList
通过 builderAction
内部的方法调用智能推导出泛型参数的类型,从而减少模板代码// 可省略
val list = buildList {add("a")
add("b")
}// 不可省略
val list = buildList> {add("a")
add("b")
val x = get(1)
}
但是
BuilderInterface
的类型推导限制比较多,比如 lambda 中调用的方法的签名要求比较严格,必须参数是泛型且返回值没有泛型,破坏了规则,类型推导失败了。所以上面代码中 lambda 有 get()
调用时,就必须清楚的标记泛型类型。这使得集合类的 builder 函数使用起来不那么灵活。Kotlin 1.6 起 BuilderInterface 没有了类似限制,对我们来说最直观好处就是 Builder 函数内怎样的调用都不会受限制,使用更加自由
val list = buildList {add("a")
add("b")
set(1, null) //OK
val x = get(1) //OK
if (x != null) {removeAt(1) //OK
}
}val map = buildMap {put("a", 1) //OK
put("b", 1.1) //OK
put("c", 2f) //OK
}
此 feature 在 1.5.30 也可以通过 添加
-Xunrestricted-builder-inference
编译器选项生效,1.6 已经是默认生效了。5. 递归泛型的类型推导 这个 feature 我们平常需求比较少。
【Kotlin|Kotlin 1.6 正式发布,都有哪些新特性()】Java 或者 Kotlin 中我们可以像下面这样定义有递归关系的泛型,即泛型的上限是它本身
public class PostgreSQLContainerextends PostgreSQLContainer>> extends JdbcDatabaseContainer> {//...
}
这种情况下的类型推导比较困难,Kotlin 1.5.30 开始可以只基于泛型的上线进行类型推导。
// Before 1.5.30
val containerA = PostgreSQLContainer(DockerImageName.parse("postgres:13-alpine")).apply {withDatabaseName("db")
withUsername("user")
withPassword("password")
withInitScript("sql/schema.sql")
}// With compiler option in 1.5.30 or by default starting with 1.6.0
val containerB = PostgreSQLContainer(DockerImageName.parse("postgres:13-alpine"))
.withDatabaseName("db")
.withUsername("user")
.withPassword("password")
.withInitScript("sql/schema.sql")
1.5.30 支持此 feature 需要添加
-Xself-upper-bound-inference
编译选项, 1.6 开始默认支持。6. 注解相关的一些优化 Kotlin 1.6 中对注解进行了诸多优化,在编译器注解处理过程中将发挥作用
支持注解的实例化
annotation class InfoMarker(val info: String)
fun processInfo(marker: InfoMarker) = ...
fun main(args: Array>) {if (args.size != 0)
processInfo(getAnnotationReflective(args))
else
processInfo(InfoMarker("default"))
}
Java 的注解本质是实现了
Annotation
的接口,可以被继承使用@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface JavaClassAnno {String[] value();
}public interface JavaClassAnno extends Annotation{//...
}class MyAnnotation implements JavaClassAnno {
// <--- works in Java
//...
}
但是在 Kotlin 中无法继承使用,这导致有一些接受注解类的 API 在 Kotlin 侧无法调用。
class MyAnnotationLiteral : JavaClassAnno {
// <--- doesn't work in Kotlin (annotation can not be inherited)
//...
}
注解类可以实例化之后,可以调用接收注解类参数的 API,能够与 Java 代码进行更好地兼容
泛型参数可添加注解
@Target(AnnotationTarget.TYPE_PARAMETER)
annotation class BoxContentclass Box<@BoxContent T> {
}
Kotlin 1.6 之后可以为泛型参数添加注解,这将为 KAPT / KSP 等注解处理器中提供方便。
可重复的运行时注解 Jdk 1.8 引入了
@java.lang.annotation.Repetable
元注解,允许同一个注解被添加多次。 Kotlin 也相应地引入了 @kotlin.annotation.Repeatable
,不过 1.6之前只能注解 @Retention(RetentionPolicy.SOURCE)
的注解,当非 SOURCE 的注解出现多次时,会报错ERROR: [NON_SOURCE_REPEATED_ANNOTATION] Repeatable annotations with non-SOURCE retention are not yet supported
此外,Kotlin 侧代码也不能使用 Java 的
@Repeatable
注解来注解多次。Kotlin1.6 开始,取消了只能用在 SOURCE 类注解的限制,任何类型的注解都可以出现多次,而且 Kotlin 侧支持使用 Java 的
@Repeatable
注解@Repeatable(AttributeList.class)
@Target({
ElementType.TYPE})
@Retentioin(RetentionPolicy.RUNTIME) //虽然是 RUNTIME 注解
annotation class Attribute(val name: String)@Attribute("attr1") //OK
@Attribute("attr2") //OK
class MyClass {
}
最后 上述介绍的是 Kotlin1.6 在语法方面的一些新特性,大部分在 1.5.30 中作为 preview 功能已经出现过,这次在 1.6 中进行了转正。除了新的语法特性,1.6 在各平台 Compiler 上有诸多新内容,我们在平日开发中接触不到本文就不介绍了。
更多内容参考:https://kotlinlang.org/docs/whatsnew16.html
推荐阅读
- android防止连续点击的简单实现(kotlin)
- retrofit2-kotlin-coroutines-adapter|retrofit2-kotlin-coroutines-adapter 超时引起的崩溃
- 【译】Rails|【译】Rails 5.0正式发布(Action Cable,API模式等)
- Kotlin泛型的高级特性(六)
- Kotlin基础(10)-代理模式在kotlin中的使用
- Android|Android Kotlin实现AIDL跨进程通信
- 残冬断想
- 早安晨语,2021-05-01
- 考霸训练营正式课5
- Windows|Windows 10 LTSC 2019 正式版轻松激活教程