前言
和严格古老的 Java 相比,Kotlin 中额外提供了不少高级语法特性。
这些高级特性中,定义于 Kotlin 的 Standard.kt
为我们提供了一些内置拓展函数以方便我们写出更优雅的代码。
相比大多数人都用过 let 函数来做过 Null Check,和 let 函数一样,with, run, apply, also 都可以提供非常强大的功能用以优化代码。
let
当需要定义一个变量在一个特定的作用域时,可以考虑使用 let 函数。当然,更多的是用于避免 Null 判断。
在 let 函数内部,用 it 指代调用 let 函数的对象,并且最后返回最后的计算值
一般结构
1 | any.let { |
具体使用
1 | fun main() { |
对应到实际使用场景一般是 需要对一个可能为 null 的对象多次做空判断:
1 | textView?.text = "TextSetInTextView" |
使用 let 函数优化后:
1 | textView?.let { |
with
和 let 类似,又和 let 不同,with 最后也包含一段函数块,也是将最后的计算的结果返回。
但是 with 不是以拓展的形式存在的。其将某个对象作为函数的参数,并且以 this 指代。
首先来看 with 的一般结构:
一般结构
1 | whith(any) { |
其实 with 函数的原始写法应该是:
1 | with(any, { |
有用过 Groove DSL 的同学一定都知道在 Groovy 中,函数调用的最后一个参数是函数的话,函数的大括号可以提到圆括号() 的外面。
巧了,Kotlin DSL 也支持,所以最终就变成了一般结构中的那种写法了。
没错,Kotlin 也是支持 DSL 的,Android 使用 Gradle 进行编译,build.gradle
使用 Groovy 进行编写。
如果你对 Groovy 不太熟悉的话,也可以使用 Kotlin DSL 来写 build.gradle.kts
。
具体使用
1 | class Person(val name: String, val age: Int) |
在 let 函数的实际使用中,我们对 textView 进行空判断,但是每次函数调用的时候还是要使用 it 对象去调用。
如果我们使用 with 函数的话,由于代码块中传入的是 this,而不是 it,那么我们就可以直接写出函数名(属性)来进行相应的设置:
1 | if (textView == null) return |
这段代码唯一的缺点就是要事先判空了,有没有既能像 let 那样能优雅的判空,又能写出这样的便利的代码呢?
别着急,咱们接着往下看。
run
刚刚说到,我们想能有 let 函数那样又优雅的判空,又能有 with 函数省去同一个对象多次设置属性的便捷写法。
没错,就是这就非我们 run 函数莫属了。run 函数基本是 let 和 with 的结合体,对象调用 run 函数,接收一个 lambda 函数为参数,传入 this 并以闭包形式返回,返回值是最后的计算结果。
一般结构
1 | any.run { |
那么上面 TextView 设置各种属性的优化写法就是这样的:
1 | textView?.run { |
像上面这个例子,在需要多次设置属性,但设置属性后返回值不是改对象(或无返回值:Unit)不能链式调用的时候,就非常适合使用 run 函数。
apply
apply 函数和 run 函数很像,但是 apply 最后返回的是调用对象自身。
一般结构
1 | val result = any.apply { |
由于 apply 函数返回的是调用对象自身,我们可以借助 apply 函数的特性进行多级判空。
具体使用
在 Java 中多级判空一直是老大难的问题:
下面是一个 School 类中包含内部类 Class,在 Class 又包含内部类 Student,我们想获取该 Student 的 name 属性的示例。
这其中包含对 className 的修改操作。
1 | public class Main { |
实际情况中可能会有更多的判空层级,如果我们用 Kotlin 的 apply 函数来操作又会是怎么样呢?
1 | fun main() { |
also
有没有注意到上面的示例中,我们最后打印该学生的名字的时候,调用了 also 函数。
没错,和 let 函数类似,唯一的区别就是 also 函数的返回值是调用对象本身,在上例中 also 函数将返回 school.mClass.student.name
。
一般结构
1 | val result = any.also { |
总结
函数定义见下表:
函数名 | 实现 |
---|---|
let | public inline fun <T, R> T.let(block: (T) -> R): R = block(this) |
with | public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block() |
run | public inline fun <T, R> T.run(block: T.() -> R): R = block() |
apply | public inline fun T.apply(block: T.() -> Unit): T { block(); return this } |
also | public inline fun T.also(block: (T) -> Unit): T { block(this); return this } |
具体的调用情况见下图: