标准函数和静态方法

学完了 Kotlin 的基础知识而已,今天我们来学习 Kotlin 的标准函数和静态方法。

标准函数 with、run 和 apply

Kotlin 的标准函数指的是 Standard.kt 文件中定义的函数,任何 Kotlin 代码都可以自由地调用所有的标准函数。

快速入门 kotlin 编程 中,我们已经学习了 let 这个标准函数,它的主要作用就是配合 ?. 操作符来进行辅助判空处理,这里就不再赘述了。

with 标准函数

with 函数接收两个参数:第一个参数可以是一个任意类型的对象,第二个参数是一个 Lambda 表达式。with 函数会在 Lambda 表达式中提供第一个参数对象的上下文,并使用 Lambda 表达式中的最后一行代码作为返回值返回。示例代码如下:

val result = with(obj) {
    // 这里是 obj 的上下文
    "value" // with 函数的返回值
}

那么这个函数有什么作用呢?它可以在连续调用同一个对象的多个方法时让代码变得更加精简,下面我们来看一个具体的例子。

比如有一个水果列表,现在我们想吃完所有水果,并将结果打印出来,就可以这样写:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val builder = StringBuilder()
builder.append("Start eating fruits.\n")
for (fruit in list) {
    builder.append(fruit).append("\n")
}
builder.append("Ate all fruits.")
val result = builder.toString()
println(result)

仔细观察上述代码,你会发现我们连续调用了很多次 builder 对象的方法。其实这个时候就可以考虑使用 with 函数来让代码变得更加精简,如下所示:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val result = with(StringBuilder()) {
    append("Start eating fruits.\n")
    for (fruit in list) {
            append(fruit).append("\n")
    }
    append("Ate all fruits.")
    toString()
}
println(result)

这段代码乍一看可能有点迷惑性,其实很好理解。首先我们给 with 函数的第一个参数传入了一个 StringBuilder 对象,那么接下来整个 Lambda 表达式的上下文就会是这个 StringBuilder 对象。于是我们在 Lambda 表达式中就不用再像刚才那样调用 builder.append() 和 builder.toString() 方法了,而是可以直接调用 append() 和 toString() 方法。Lambda 表达式的最后一行代码会作为 with 函数的返回值返回,最终我们将结果打印出来。

run 标准函数

run 函数的用法和使用场景其实和 with 函数是非常类似的,只是稍微做了一些语法改动而已。首先 run 函数通常不会直接调用,而是要在某个对象的基础上调用;其次 run 函数只接收一个 Lambda 参数,并且会在 Lambda 表达式中提供调用对象的上下文。其他方面和 with 函数是一样的,包括也会使用 Lambda 表达式中的最后一行代码作为返回值返回。示例代码如下:

val result = obj.run {
    // 这里是 obj 的上下文
    "value" // run 函数的返回值
}

那么现在我们就可以使用 run 函数来修改一下吃水果的这段代码,如下所示:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val result = StringBuilder().run {
    append("Start eating fruits.\n")
    for (fruit in list) {
            append(fruit).append("\n")
    }
    append("Ate all fruits.")
    toString()
}
println(result)

总体来说变化非常小,只是将调用 with 函数并传入 StringBuilder 对象改成了调用 StringBuilder 对象的 run 方法,其他都没有任何区别,这两段代码最终的执行结果是完全相同的。

apply 标准函数

apply 函数和 run 函数也是极其类似的,都要在某个对象上调用,并且只接收一个 Lambda 参数,也会在 Lambda 表达式中提供调用对象的上下文,但是 apply 函数无法指定返回值,而是会自动返回调用对象本身。示例代码如下:

val result = obj.apply {
    // 这里是 obj 的上下文
}
// result == obj

那么现在我们再使用 apply 函数来修改一下吃水果的这段代码,如下所示:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val result = StringBuilder().apply {
    append("Start eating fruits.\n")
    for (fruit in list) {
            append(fruit).append("\n")
    }
    append("Ate all fruits.")
}
println(result.toString())

注意这里的代码变化,由于 apply 函数无法指定返回值,只能返回调用对象本身,因此这里的 result 实际上是一个 StringBuilder 对象,所以我们在最后打印的时候还要再调用它的 toString() 方法才行。这段代码的执行结果和前面两段仍然是完全相同的,我就不再重复演示了。

这样我们就将 Kotlin 中最常用的几个标准函数学完了,你会发现其实 with、run 和 apply 这几个函数的用法和使用场景是非常类似的。在大多数情况下,它们可以相互转换,但你最好还是要掌握它们之间的区别,以便在编程时能够作出最佳的选择。

回想一下刚刚在最佳实践环节编写的启动 Activity 的代码:

val intent = Intent(context, SecondActivity::class.java)
intent.putExtra("param1", "data1")
intent.putExtra("param2", "data2")
context.startActivity(intent)

这里每传递一个参数就要调用一次 intent.putExtra() 方法,如果要传递 10 个参数,那就得调用 10 次。对于这种情况,我们就可以使用标准函数来对代码进行精简,如下所示:

val intent = Intent(context, SecondActivity::class.java).apply {
    putExtra("param1", "data1")
    putExtra("param2", "data2")    
}
context.startActivity(intent)

可以看到,由于 Lambda 表达式中的上下文就是 Intent 对象,所以我们不再需要调用 intent.putExtra() 方法,而是直接调用 putExtra() 方法就可以了。传递的参数越多,这种写法的优势也就越明显。

定义静态方法

静态方法在某些编程语言里面又叫作类方法,指的就是那种不需要创建实例就能调用的方法,所有主流的编程语言都会支持静态方法这个特性。

在 Java中 定义一个静态方法非常简单,只需要在方法上声明一个static关键字就可以了,如下所示:

public class Util {
    public static void doAction() {
        System.out.println("do action");
    }
}

这是一个非常简单的工具类,上述代码中的 doAction() 方法就是一个静态方法。调用静态方法并不需要创建类的实例,而是可以直接以 Util.doAction() 这种写法来调用。因而静态方法非常适合用于编写一些工具类的功能,因为工具类通常没有创建实例的必要,基本是全局通用的。

但是和绝大多数主流编程语言不同的是,Kotlin 却极度弱化了静态方法这个概念,想要在 Kotlin 中定义一个静态方法反倒不是一件容易的事。

那么 Kotlin 为什么要这样设计呢?因为 Kotlin 提供了比静态方法更好用的语法特性,并且我们在上一节中已经学习过了,那就是单例类。

单例类

像工具类这种功能,在 Kotlin 中就非常推荐使用单例类的方式来实现,比如上述的 Util 工具类,如果使用 Kotlin 来实现的话就可以这样写:

object Util {
    fun doAction() {
        println("do action")
    }
}

虽然这里的 doAction() 方法并不是静态方法,但是我们仍然可以使用 Util.doAction() 的方式来调用,这就是单例类所带来的便利性。

companion object

不过,使用单例类的写法会将整个类中的所有方法全部变成类似于静态方法的调用方式,而如果我们只是希望让类中的某一个方法变成静态方法的调用方式该怎么办呢?这个时候就可以使用刚刚在最佳实践环节用到的 companion object 了,示例如下:

class Util {
    fun doAction() {
        println("do action")
    }

    companion object {
        fun doAction2() {
            println("do action2")
        }
    }
}

这里首先我们将 Util 从单例类改成了一个普通类,然后在类中直接定义了一个 doAction1() 方法,又在 companion object 中定义了一个 doAction2() 方法。现在这两个方法就有了本质的区别,因为 doAction1() 方法是一定要先创建 Util 类的实例才能调用的,而 doAction2() 方法可以直接使用 Util.doAction2() 的方式调用。

不过,doAction2() 方法其实也并不是静态方法,companion object 这个关键字实际上会在 Util 类的内部创建一个伴生类,而 doAction2() 方法就是定义在这个伴生类里面的实例方法。只是 Kotlin 会保证 Util 类始终只会存在一个伴生类对象,因此调用 Util.doAction2() 方法实际上就是调用了 Util 类中伴生对象的 doAction2() 方法。

由此可以看出,Kotlin 确实没有直接定义静态方法的关键字,但是提供了一些语法特性来支持类似于静态方法调用的写法,这些语法特性基本可以满足我们平时的开发需求了。

然而如果你确确实实需要定义真正的静态方法,Kotlin 仍然提供了两种实现方式:注解和顶层方法。下面我们来逐个学习一下。

注解

先来看注解,前面使用的单例类和 companion object 都只是在语法的形式上模仿了静态方法的调用方式,实际上它们都不是真正的静态方法。因此如果你在 Java 代码中以静态方法的形式去调用的话,你会发现这些方法并不存在。而如果我们给单例类或 companion object 中的方法加上 @JvmStatic 注解,那么 Kotlin 编译器就会将这些方法编译成真正的静态方法,如下所示:

class Util {
    fun doAction1() {
        println("do action1")
    }

    companion object {
        @JvmStatic
        fun doAction2() {
            println("do action2")
        }
    }
}

注意,@JvmStatic 注解只能加在单例类或 companion object 中的方法上,如果你尝试加在一个普通方法上,会直接提示语法错误。

由于 doAction2() 方法已经成为了真正的静态方法,那么现在不管是在 Kotlin 中还是在 Java 中,都可以使用 Util.doAction2() 的写法来调用了。

顶层方法

再来看顶层方法,顶层方法指的是那些没有定义在任何类中的方法,比如我们在上一节中编写的 main() 方法。Kotlin 编译器会将所有的顶层方法全部编译成静态方法,因此只要你定义了一个顶层方法,那么它就一定是静态方法。

想要定义一个顶层方法,首先需要创建一个 Kotlin 文件。对着任意包名右击 → New → KotlinFile/Class,在弹出的对话框中输入文件名即可。注意创建类型要选择 File。

点击 “OK” 完成创建,这样刚刚的包名路径下就会出现一个 Helper.kt 文件。现在我们在这个文件中定义的任何方法都会是顶层方法,比如这里我就定义一个 doSomething() 方法吧,如下所示:

fun doSomething() {
    println("do something")
}

刚才已经讲过了,Kotlin 编译器会将所有的顶层方法全部编译成静态方法,那么我们要怎么调用这个 doSomething() 方法呢?

如果是在 Kotlin 代码中调用的话,那就很简单了,所有的顶层方法都可以在任何位置被直接调用,不用管包名路径,也不用创建实例,直接键入 doSomething() 即可,如下图所示:

image.png

但如果是在 Java 代码中调用,你会发现是找不到 doSomething() 这个方法的,因为 Java 中没有顶层方法这个概念,所有的方法必须定义在类中。那么这个 doSomething() 方法被藏在了哪里呢?我们刚才创建的 Kotlin 文件名叫作 Helper.kt,于是 Kotlin 编译器会自动创建一个叫作 HelperKt 的 Java 类,doSomething() 方法就是以静态方法的形式定义在 HelperKt 类里面的,因此在 Java 中使用 HelperKt.doSomething() 的写法来调用就可以了,如下图所示:

1627010366(1).jpg

好了,关于静态方法的相关内容就学到这里。本小节中所学的知识,除了 @JvmStatic 注解不太常用之外,其他像单例类、companion object、顶层方法都是 Kotlin 中十分常用的技巧,希望你能将它们牢牢掌握。


Maenj_Ba_lah
28 声望7 粉丝

真正的大师,永远怀着一颗学徒的心。