本文主要參考:郭霖《第一行代碼》 Kotlin部分學習記錄
目錄
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()
}
}