Kotlin 1.4 中的语法改动

Kotlin 1.4在8月17号发布了,如果你使用的是IntelliJ IDEA这个IDE,那么IDE会提示你升级 Kotlin 的插件。Kotlin 1.4 这个新版本侧重在提升质量和性能,其目标是提升使用 Kotlin 开发时的体验,包括提升了代码自动完成提示、语法高亮和编译错误报告的速度。除此之外,Kotlin 1.4 还对语法做了一些修改,本文对 Kotlin 1.4 中与语法相关的更新做一下介绍。

Kotlin接口的SAM转换

SAM(Single Abstract Method,单一抽象方法)是Java 8中随着Lambda表达式而引入的概念。SAM指的是接口中只有一个抽象方法。包含SAM的接口称为函数式接口,可以通过 @FunctionalInterface 注解来声明。java.lang.Runnable 就是典型的函数式接口,其中的 run 方法就是SAM。SAM是为了配合Lambda表达式而定义的。通过Lambda表达式,我们可以创建函数式接口的实例对象,而Lambda表达式会成为函数式接口中SAM方法的实现体。

在Kotlin中,函数是一等公民。Kotlin中有函数常量。Kotlin支持从SAM到函数常量的转换。在下面的代码中,task 是一个函数常量,它被提供给 ExecutorServicesubmit 方法,Kotlin会自动把 task 转换成 Runnable 接口的对象。

fun main(args: Array<String>) {
    val task = {
        println("运行")
    }
    val executor = Executors.newSingleThreadExecutor()
    executor.submit(task)
    executor.shutdown()
    executor.awaitTermination(10, TimeUnit.SECONDS)
}

在 Kotlin 1.4 之前,SAM和函数常量的转换只能对Java接口进行。Kotlin 1.4 把这种支持扩展到了Kotlin中的接口。只包含SAM的Kotlin接口可以使用 fun 来声明,如下面的代码所示。

fun interface TaskProcessor {
    fun process()
}

下面代码中的 process 方法接收一个 TaskProcessor 接口的对象。

fun process(processor: TaskProcessor) {
    processor.process()
}

同样的 task 对象,也可以由 process 方法来运行,因为 TaskProcessorRunnable 中的SAM的方法有相同的签名。

fun main(args: Array<String>) {
    val task = {
        println("运行")
    }
    process(task)
}

库作者的显式API模式

如果你使用 Kotlin 开发的是共享库,那么可以启用 Kotlin 1.4 新增的显式API模式。这个模式的特点是编译器会对库暴露的API进行额外的检查,以确保API的一致性。

所进行的额外检查包括:

  • 如果声明默认的可见性是公开(public)的,那么需要添加显式的可见性声明。
  • 公开API中的属性和方法都需要显式的类型声明。

显式的声明避免了无意开放的API。显式API模式中的检查可以有严格和警告两种模式。在严格模式下,编译器会对发现的问题给出错误,从而阻止构建过程;而警告模式下则只会给出警告。

下面的 Kotlin 构建脚本展示了启用这两种模式的做法。

kotlin {    
    // 严格模式
    explicitApi() 
    // 或者
    explicitApi = ExplicitApiMode.Strict
    
    // 警告模式
    explicitApiWarning()
    // 或
    explicitApi = ExplicitApiMode.Warning
}

命名参数和位置参数的混用

在 Kotlin 中,当调用一个方法时,可以根据参数名称或位置来传递参数值。在 Kotlin 1.3 中,命名参数必须出现在所有位置参数的后面。Kotlin 1.4 移除了这个限制,可以自由地混用命名参数和位置参数,只要这些参数值出现在正确的位置上。

下面的代码展示了命名参数和位置参数的混用。方法 fun1 的参数 a2a3 都是 Boolean 类型,如果不通过命名参数来调用,在阅读代码时很难快速发现参数值的含义。在两种调用方式中,很明显的第一种的可读性更高。

fun fun1(a1: String, a2: Boolean, a3: Boolean, a4: Int) {

}

fun1("Hello", a2 = true, a3 = false, 3) // 可读性更好
fun1("Hello", true, false, 3)

末尾逗号

在 Kotlin 1.4 中,可以在方法的形式参数和实际参数的列表中,when 表达式中的条目中,以及解构声明的组件中,添加额外的末尾逗号。末尾逗号的好处是只需要改动当前行就可以完成项目的添加和位置的移动。

下面的代码展示了末尾逗号的用法。

fun fun2(
    v1: String,
    v2: Int,
) {
    val list = listOf(
        1,
        2,
        3,
    )
}

可调用的引用

Kotlin 1.中增强了对可调用的引用(callable reference)的支持,体现在下面的 4 个方面。

  1. 如果函数的参数有默认值,可以创建它的引用。在下面的代码中,函数 foo 的参数 i 有默认值 0 ,在 main 方法中可以通过 ::foo 来引用。
fun foo(i: Int = 0): String = "$i!"

fun apply(func: () -> String): String = func()

fun main() {
    println(apply(::foo))
}
  1. 如果需要使用的函数的返回值类型是 Unit ,那么可以用返回任意类型的函数引用来替代,前提是参数类型匹配。在下面的代码中,函数 foo 的参数 f 的返回值类型是 Unit ,而 bar 的返回值类型的 String ,可以使用 bar 的引用来调用函数 foo
fun foo(f: () -> Unit) { }
fun bar() = "Hello"

fun main() {
    foo(::bar)
}
  1. 如果一个函数的参数长度可变,那么该函数的引用可以根据实际需要的参数数量来自行适应。在下面的代码中,函数 foo 的参数 y 是可变长度的,而 use0use1use2 都可以使用 foo 的引用,虽然函数 f 的参数数量不同。
fun foo(x: Int, vararg y: String) {}

fun use0(f: (Int) -> Unit) {}
fun use1(f: (Int, String) -> Unit) {}
fun use2(f: (Int, String, String) -> Unit) {}

fun main() {
    use0(::foo)
    use1(::foo)
    use2(::foo)
}
  1. 在 Kotlin 1.4 中,可暂停的方法引用可以自动转换。在下面的代码中,takeSuspend 的参数要求是可暂停的函数引用,可以用一般的函数 call 来代替。
fun call() {}
fun takeSuspend(f: suspend () -> Unit) {}

fun main() {
    takeSuspend(::call)
}

循环中的when

在 Kotlin 1.4 中,循环中的 when 表达式可以直接使用 breakcontinue 来跳出最近的循环。下面代码的输出为 3 和 4。

fun test(xs: List<Int>) {
    for (x in xs) {
        when (x) {
            1 -> continue
            2 -> break
            else -> println(x)
        }
    }
}

fun main() {
    test(listOf(3, 1, 4, 2, 5))
}

Kotlin 1.4 中语法相关的改动就介绍到这里。

版权所有 © 2024 灵动代码