Kotlin 中的 let, with, run, apply, also 等函數的使用

前言

和嚴格古老的 Java 相比,Kotlin 中額外提供了很多高級語法特性。 這些高級特性中,定義於 Kotlin 的 Standard.kt 爲咱們提供了一些內置拓展函數以方便咱們寫出更優雅的代碼。java

相比大多數人都用過 let 函數來作過 Null Check,和 let 函數同樣,with, run, apply, also 均可以提供很是強大的功能用以優化代碼。git

let

當須要定義一個變量在一個特定的做用域時,能夠考慮使用 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
}
複製代碼

with

和 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 那樣能優雅的判空,又能寫出這樣的便利的代碼呢?

彆着急,我們接着往下看。

run

剛剛說到,咱們想能有 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

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

有沒有注意到上面的示例中,咱們最後打印該學生的名字的時候,調用了 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 }

具體的調用狀況見下圖:

kotlin-fun-useage.png
相關文章
相關標籤/搜索