和嚴格古老的 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 } |
具體的調用狀況見下圖: