和严格古老的 Java 相比,Kotlin 中额外提供了很多高级语法特性。 这些高级特性中,定义于 Kotlin 的 Standard.kt 为咱们提供了一些内置拓展函数以方便咱们写出更优雅的代码。java
相比大多数人都用过 let 函数来作过 Null Check,和 let 函数同样,with, run, apply, also 均可以提供很是强大的功能用以优化代码。git
当须要定义一个变量在一个特定的做用域时,能够考虑使用 let 函数。固然,更多的是用于避免 Null 判断。github
在 let 函数内部,用 it 指代调用 let 函数的对象,而且最后返回最后的计算值markdown
any.let { // 用 it 指代 any 对象 // todo() 是 any 对象的共有属性或方法 // it.todo() 的返回值做为 let 函数的返回值返回 it.todo() } // 另外一种用法 any?.let { it.todo() // any 不为 null 时才会调用 let 函数 } 复制代码
fun main() { val result = "Test".let { println(it) // Test 3 * 4 // result = 12 } println(result) // 12 } 复制代码
对应到实际使用场景通常是 须要对一个可能为 null 的对象屡次作空判断:闭包
textView?.text = "TextSetInTextView" textView?.setTextColor(ContextCompat.getColor(this, R.color.colorAccent)) textView?.textSize = 18f 复制代码
使用 let 函数优化后:app
textView?.let { it.text = "TextSetInTextView" it.setTextColor(ContextCompat.getColor(this, R.color.colorAccent)) it.textSize = 18f } 复制代码
和 let 相似,又和 let 不一样,with 最后也包含一段函数块,也是将最后的计算的结果返回。函数
可是 with 不是以拓展的形式存在的。其将某个对象做为函数的参数,而且以 this 指代。oop
首先来看 with 的通常结构:gradle
whith(any) { // todo() 是 any 对象的共有属性或方法 // todo() 的返回值做为 with 函数的返回值返回 todo() } 复制代码
其实 with 函数的原始写法应该是:优化
with(any, {
todo()
})
复制代码
有用过 Groove DSL 的同窗必定都知道在 Groovy 中,函数调用的最后一个参数是函数的话,函数的大括号能够提到圆括号() 的外面。
巧了,Kotlin DSL 也支持,因此最终就变成了通常结构中的那种写法了。
没错,Kotlin 也是支持 DSL 的,Android 使用 Gradle 进行编译,build.gradle
使用 Groovy 进行编写。
若是你对 Groovy 不太熟悉的话,也可使用 Kotlin DSL 来写 build.gradle.kts
。
class Person(val name: String, val age: Int) fun main() { val chengww = Person("chengww", 18) val result = with(chengww) { println("Greetings. My name is $name, I am $age years old.") 3 * 4 // result = 12 } println(result) } 复制代码
在 let 函数的实际使用中,咱们对 textView 进行空判断,可是每次函数调用的时候仍是要使用 it 对象去调用。
若是咱们使用 with 函数的话,因为代码块中传入的是 this,而不是 it,那么咱们就能够直接写出函数名(属性)来进行相应的设置:
if (textView == null) return with(textView) { text = "TextSetInTextView" setTextColor(ContextCompat.getColor(this@TestActivity, R.color.colorAccent)) textSize = 18f } 复制代码
这段代码惟一的缺点就是要事先判空了,有没有既能像 let 那样能优雅的判空,又能写出这样的便利的代码呢?
别着急,我们接着往下看。
刚刚说到,咱们想能有 let 函数那样又优雅的判空,又能有 with 函数省去同一个对象屡次设置属性的便捷写法。
没错,就是这就非咱们 run 函数莫属了。run 函数基本是 let 和 with 的结合体,对象调用 run 函数,接收一个 lambda 函数为参数,传入 this 并以闭包形式返回,返回值是最后的计算结果。
any.run { // todo() 是 any 对象的共有属性或方法 // todo() 的返回值做为 run 函数的返回值返回 todo() } 复制代码
那么上面 TextView 设置各类属性的优化写法就是这样的:
textView?.run { text = "TextSetInTextView" setTextColor(ContextCompat.getColor(this@TestActivity, R.color.colorAccent)) textSize = 18f } 复制代码
像上面这个例子,在须要屡次设置属性,但设置属性后返回值不是改对象(或无返回值:Unit)不能链式调用的时候,就很是适合使用 run 函数。
apply 函数和 run 函数很像,可是 apply 最后返回的是调用对象自身。
val result = any.apply { // todo() 是 any 对象的共有属性或方法 todo() 3 * 4 // 最后返回的是 any 对象,而不是 12 } println(result) // 打印的是 any 对象 复制代码
因为 apply 函数返回的是调用对象自身,咱们能够借助 apply 函数的特性进行多级判空。
在 Java 中多级判空一直是老大难的问题:
下面是一个 School 类中包含内部类 Class,在 Class 又包含内部类 Student,咱们想获取该 Student 的 name 属性的示例。
这其中包含对 className 的修改操做。
public class Main { public static void main(String[] args) { School school = init(); // To change the className of the a student and get his(her) name in this school what we should do in Java if (school != null && school.mClass != null) { school.mClass.className = "Class 1"; System.out.println("Class name has been changed as Class 1."); if (school.mClass.student != null) { System.out.println("The student's name is " + school.mClass.student.name); } } } static School init() { School school = new School(); school.mClass = new School.Class(); school.mClass.student = new School.Class.Student(); school.mClass.student.name = "chengww"; return school; } static class School { Class mClass; private static class Class { String className; Student student; private static class Student { String name; } } } } 复制代码
实际状况中可能会有更多的判空层级,若是咱们用 Kotlin 的 apply 函数来操做又会是怎么样呢?
fun main() { val school = init() school?.mClass?.apply { className = "Class 1" println("Class name has been changed as Class 1.") }?.student?.name?.also { println("The student's name is $it.") } } fun init(): School = School(School.Class(School.Class.Student("chengww"))) class School(var mClass: Class? = null) { class Class(var student: Student? = null, var className: String? = null) { class Student(var name: String? = null) } } 复制代码
有没有注意到上面的示例中,咱们最后打印该学生的名字的时候,调用了 also 函数。
没错,和 let 函数相似,惟一的区别就是 also 函数的返回值是调用对象自己,在上例中 also 函数将返回 school.mClass.student.name
。
val result = any.also { // 用 it 指代 any 对象 // todo() 是 any 对象的共有属性或方法 it.todo() 3 * 4 // 将返回 any 对象,而不是 12 } 复制代码
函数定义见下表:
函数名 | 实现 |
---|---|
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 } |
具体的调用状况见下图: