Kotlin 1.6 正式发布,带来哪些新特性?

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
简介: Kotlin 1.6 正式发布,带来哪些新特性?

1. 更安全的 when 语句

Kotlin 的 when 关键字允许我们在 case 分支中写表达式或者语句。1.6 之前在 case 分支写语句时存在安全隐患:

// 定义枚举 
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,否则编译出错 Case 分支必须包含 else,否则编译出错
when语句 case 可以不穷举所有分支,不会报错 同上

可见,当 x 是可穷举类型时,编译器对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<T>(var param: P): () -> Result<T> {
    override fun invoke(): Result<T> {
        // 基于成员 param 自定义逻辑
    }
}
fun <T> handle(handler: () -> Result<T>) {
    //...
}

Kotlin 代码中大量使用各种函数类型,许多方法都以函数类型作为参数。当你需要调用这些方法时,需要传入一个函数类型的实例。而当你想在实例中封装一些可复用的逻辑时,可以使用函数类型作为父类创建子类。

但是这种做法目前不适用于挂起函数,你无法继承一个 suspend 函数类型的父类

class C : suspend () -> Unit { // Error: Suspend function type is not allowed as supertypes 
}
C().startCoroutine(completion = object : Continuation<Unit> {
    override val context: CoroutineContext
        get() = TODO("Not yet implemented")
    override fun resumeWith(result: Result<Unit>) {
        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 <T1, T2, R> combine(
    flow: Flow<T1>, flow2: Flow<T2>, 
    transform: suspend (a: T1, b: T2) -> R): Flow<R>
     = 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")
    }
}

如上述代码所示,flowcombine 方法其参数 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函数,比如 buildListbuildMap 之类。

@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun <E> buildList(@BuilderInference builderAction: MutableList<E>.() -> Unit): List<E> {
    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
    return buildListInternal(builderAction)
}
@kotlin.ExperimentalStdlibApi
val list = buildList<String> {
    add("a")
    add("b")
}

buildList 的实现中使用 @BuilderInterface 注解了 builderAction 这个 lambda 。这样可以在调用时 buildList 通过 builderAction 内部的方法调用智能推导出泛型参数的类型,从而减少模板代码

//<String> 可省略
val list = buildList {
    add("a")
    add("b")
}
//<String> 不可省略
val list = buildList<String> {
    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 我们平常需求比较少。

Java 或者 Kotlin 中我们可以像下面这样定义有递归关系的泛型,即泛型的上限是它本身

public class PostgreSQLContainer<SELF extends PostgreSQLContainer<SELF>> extends JdbcDatabaseContainer<SELF> {
    //...
}

这种情况下的类型推导比较困难,Kotlin 1.5.30 开始可以只基于泛型的上线进行类型推导。

// Before 1.5.30
val containerA = PostgreSQLContainer<Nothing>(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<String>) {
    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 BoxContent
class 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 上有诸多新内容,我们在平日开发中接触不到本文就不介绍了。

更多内容参考:kotlinlang.org/docs/whatsn…

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
6月前
|
IDE Java 开发工具
Kotlin 1.8.0 现已发布,有那些新特性?
Kotlin 1.8.0 现已发布,有那些新特性?
104 0
Kotlin 1.8.0 现已发布,有那些新特性?
|
IDE Java 编译器
Kotlin 1.5 新特性:密封接口比密封类强在哪?
Kotlin 1.5 推出了密封接口(Sealed Interface),这与密封类(Sealed Class)有什么区别呢?
344 0
Kotlin 1.5 新特性:密封接口比密封类强在哪?
|
JavaScript 前端开发 Java
Kotlin 1.2 新特性
在Kotlin 1.1中,团队正式发布了JavaScript目标,允许开发者将Kotlin代码编译为JS并在浏览器中运行。在Kotlin 1.2中,团队增加了在JVM和JavaScript之间重用代码的可能性。
7534 0
|
Kotlin
Kotlin新特性:区间
一、概念 一个数学上的概念、表示范围 ClosedRange 的子类,IntRange最常用 二、基本的写法 package net.
834 0
|
JavaScript 前端开发 Java
|
JavaScript 前端开发 Java
Kotlin 与 Java 8 的重要新特性以及 Java 9、10 的发展规划
Java 8可谓是自Java 5以来最具革命性的版本了,她在语言、编译器、类库、开发工具以及Java虚拟机等方面都带来了不少新特性。我们来一一回顾一下这些特性。
1319 0
|
18天前
|
移动开发 Java Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【4月更文挑战第3天】在移动开发领域,性能优化一直是开发者关注的焦点。随着Kotlin的兴起,其在Android开发中的地位逐渐上升,但关于其与Java在性能方面的对比,尚无明确共识。本文通过深入分析并结合实际测试数据,探讨了Kotlin与Java在Android平台上的性能表现,揭示了在不同场景下两者的差异及其对应用性能的潜在影响,为开发者在选择编程语言时提供参考依据。
|
19天前
|
数据库 Android开发 开发者
构建高效Android应用:Kotlin协程的实践指南
【4月更文挑战第2天】随着移动应用开发的不断进步,开发者们寻求更流畅、高效的用户体验。在Android平台上,Kotlin语言凭借其简洁性和功能性赢得了开发社区的广泛支持。特别是Kotlin协程,作为一种轻量级的并发处理方案,使得异步编程变得更加简单和直观。本文将深入探讨Kotlin协程的核心概念、使用场景以及如何将其应用于Android开发中,以提高应用性能和响应能力。通过实际案例分析,我们将展示协程如何简化复杂任务,优化资源管理,并为最终用户提供更加流畅的体验。
|
28天前
|
调度 数据库 Android开发
构建高效Android应用:Kotlin协程的实践与优化
在Android开发领域,Kotlin以其简洁的语法和平台友好性成为了开发的首选语言。其中,Kotlin协程作为处理异步任务的强大工具,它通过提供轻量级的线程管理机制,使得开发者能够在不阻塞主线程的情况下执行后台任务,从而提升应用性能和用户体验。本文将深入探讨Kotlin协程的核心概念,并通过实例演示如何在实际的Android应用中有效地使用协程进行网络请求、数据库操作以及UI的流畅更新。同时,我们还将讨论协程的调试技巧和常见问题的解决方法,以帮助开发者避免常见的陷阱,构建更加健壮和高效的Android应用。
35 4
|
30天前
|
移动开发 Java Android开发
构建高效Android应用:Kotlin协程的实践之路
【2月更文挑战第31天】 在移动开发领域,性能优化和流畅的用户体验一直是开发者追求的目标。随着Kotlin语言的流行,其异步编程解决方案——协程(Coroutines),为Android应用带来了革命性的并发处理能力。本文将深入探讨Kotlin协程的核心概念、设计原理以及在Android应用中的实际应用案例,旨在帮助开发者掌握这一强大的工具,从而提升应用的性能和响应能力。