Kotlin系列三:空指針檢查

本文主要參考:郭霖《第一行代碼》 Kotlin部分學習記錄

目錄

1 可空類型(?)

2 判空輔助工具

2.1 ?.操作符

2.1 ?:操作符

8.2.1 !!操作符

2.3 let函數


Android系統上崩潰率最高的異常類型就是空指針異常(NullPointerException)。

public void doStudy(Study study) {
    if (study != null) {
        study.readBooks();
        study.doHomework();
    }
}

這種java裏常見的判空檢查容易陷入判空地獄的災難。Kotlin提供了很好的解決思路。

1 可空類型(?)

Kotlin在編譯時就進行判空檢查,這會導致代碼變得相對難寫些,因爲你得實時考慮到對象的爲空與否。

一個判空舉例:

fun doStudy(study: Study) {
    study.readBooks()
    study.doHomework()
}

如果你嘗試向doStudy()函數傳入一個null參數,則會提示錯誤:

那麼可爲空的類型系統是什麼樣的呢?很簡單,就是在類名的後面加上一個問號:

爲什麼會出現紅色報錯:由於我們將參數改成了可爲空的Study?類型,此時調用參數的readBooks()和doHomework()方法都可能造成空指針異常過。如何解決呢: 

fun doStudy(study: Study?) {
    if (study != null) {
        study.readBooks()
        study.doHomework()
    }
}

2 判空輔助工具

2.1 ?.操作符

?.操作符:當對象不爲空時正常調用相應的方法,當對象爲空時則什麼都不做(相當於外部包裹了 !=null  的一個判斷了):

fun doStudy(study: Study?) {
    study?.readBooks()
    study?.doHomework()
}

2.1 ?:操作符

?:操作符:操作符的左右兩邊都接收一個表達式,如果左邊表達式的結果不爲空就返回左邊表達式的結果,否則就返回右邊表達式的結果。

val c = if (a ! = null) {
    a
} else {
    b
}

這段代碼的邏輯使用?:操作符就可以簡化成:

val c = a ?: b

比如現在我們要編寫一個函數用來獲得一段文本的長度,使用傳統的寫法就可以這樣寫:

fun getTextLength(text: String?): Int {
    if (text != null) {
        return text.length
    }
    return 0
}

改進:

fun getTextLength(text: String?) = text?.length ?: 0

8.2.1 !!操作符

不過Kotlin的空指針檢查機制也並非總是那麼智能,有的時候我們可能從邏輯上已經將空指針異常處理了,但是Kotlin的編譯器並不知道,這個時候它還是會編譯失敗。

觀察如下的代碼示例:

var content: String? = "hello"

fun main() {
    if (content != null) {
        printUpperCase()
    }
}

fun printUpperCase() {
    val upperCase = content.toUpperCase()
    println(upperCase)
}

看上去好像邏輯沒什麼問題,但這段代碼一定是無法運行的。因爲printUpperCase()函數並不知道外部已經對content變量進行了非空檢查,在調用toUpperCase()方法時,還認爲這裏存在空指針風險,從而無法編譯通過。在這種情況下,如果我們想要強行通過編譯,可以使用非空斷言工具,寫法是在對象的後面加上!!,如下所示:

fun printUpperCase() {
    val upperCase = content!!.toUpperCase()
    println(upperCase)
}

這種寫法意在告訴Kotlin,我非常確信這裏的對象不會爲空,所以不用你來幫我做空指針檢查了,如果出現問題,你可以直接拋出空指針異常,後果由我自己承擔。

2.3 let函數

let函數屬於Kotlin中的標準函數,這個函數提供了函數式API的編程接口,並將原始調用對象作爲參數傳遞到Lambda表達式中。示例代碼如下:

obj.let { obj2 ->
    // 編寫具體的業務邏輯
}

結合doStudy()函數:

fun doStudy(study: Study?) {
    study?.readBooks()
    study?.doHomework()
}

雖然這段代碼我們通過?.操作符優化之後可以正常編譯通過,但其實這種表達方式是有點囉嗦的,如果將這段代碼準確翻譯成使用if判斷語句的寫法,對應的代碼如下:

fun doStudy(study: Study?) {
    if (study != null) {
        study.readBooks()
    }
    if (study != null) {
        study.doHomework()
    }
}

也就是說,本來我們進行一次if判斷就能隨意調用study對象的任何方法,但受制於?.操作符的限制,現在變成了每次調用study對象的方法時都要進行一次if判斷。

這個時候就可以結合使用?.操作符和let函數來對代碼進行優化了,如下所示:

fun doStudy(study: Study?) {
    study?.let { stu ->
        stu.readBooks()
        stu.doHomework()
    }
}

我來簡單解釋一下上述代碼,?.操作符表示對象爲空時什麼都不做,對象不爲空時就調用let函數,而let函數會將study對象本身作爲參數傳遞到Lambda表達式中,此時的study對象肯定不爲空了,我們就能放心地調用它的任意方法了。

另外還記得Lambda表達式的語法特性嗎?當Lambda表達式的參數列表中只有一個參數時,可以不用聲明參數名,直接使用it關鍵字來代替即可,那麼代碼就可以進一步簡化成:

fun doStudy(study: Study?) {
    study?.let {
        it.readBooks()
        it.doHomework()
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章