前言
相傳NPE值十億美金。哇~那是好多?
Kotlin爲何這麼??號稱解決了NPE呢?
① NPE從哪裏來
② Kotlin從哪幾個方面解決了NPE
③ Kotlin到底解決了什麼?
④ 在什麼時候,Kotlin的NPE解決方案是失靈的?
NPE的來源
有四個來源
- 顯式調用 throw NullPointerException()
- 使用了 !! 操作符
- 這是Kotlin提供的用戶可強制拋出NPE的操作符
- 使用了!!只要碰到NPE就會如期拋出
- 有些數據在初始化時不一致
- this泄漏
未初始化的this被傳遞,並用於其他地方 - 調用了一個 超類中的open成員,並在 派生類中使用了 該open成員的未初始化 狀態
- this泄漏
- Java 互操作
- 企圖訪問平臺類型的 null 引用的成員
- 平臺類型 即 Java 聲明的類型
- java的引用可能是null
- 平臺類型的範例
val list = ArrayList<String>() // 非空(構造函數結果) list.add("Item") val size = list.size // 非空(原生 int) val item = list[0] // 推斷爲平臺類型(普通 Java 對象)
item.substring(1) // 允許,如果 item == null 可能會拋出異常
- 具有錯誤可空性的 Java 互操作的泛型類型
真是繞口,怎麼理解呢?
比如,
你從java代碼中向Kotlin添加了一個null值到MutableList,
那自然MutableList無法處理null,
那
這個null就需要MutableList<String?>處理~ - 由外部 Java 代碼引發的其他問題
- 企圖訪問平臺類型的 null 引用的成員
如何區分類型系統是否容忍null
看個例子
fun main() {
var a: String = "abc"
a = null // 編譯錯誤
}
若 a 聲明爲String! 則 可爲空
fun main() {
var b: String? = "abc"
b = null // ok
print(b)
}
訪問 val l = a.length
這就不會報錯
訪問val l = b.length // 錯誤:變量“b”可能爲空
編譯器會報錯
在條件中檢測 null
就是用if …else進行判空
看個範例就懂了
val l = if (b != null) b.length else -1
安全的調用
這裏增加了 ?.操作符。
回憶一下java的網絡請求返回的實體
在java中是這樣的
bob.department.head.name
只要有一個環節爲null就報npe
在Kotlin中可以這樣
bob?.department?.head?.name
任一環節爲null,就會返回 null
如果要只對非空值執行某個操作
可以與 let 一起使用
範例
fun main() {
val listWithNulls: List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
item?.let { println(it+" is the result") } // 輸出 Kotlin 並忽略 null
}
}
輸出
Kotlin is the result
賦值過程中的安全調用
範例
// 如果 person
或者 person.department
其中之一爲空,都不會調用該函數:
person?.department?.head = managersPool.getManager()
在這裏範例中,只要左側返回null,就跳過右側表達式的求值
注意
不要在右側表達式做過多的處理,儘量純淨,小心處理會依賴這部分邏輯的操作,避免埋坑
Elvis 操作符
當我們有一個可空的引用 r 時,我們可以說“如果 r 非空,我使用它;否則使用某個非空的值 x”
一般是這樣寫的
val l: Int = if (r != null) r.length else -1
通過 Elvis 操作符,即?:操作符
這個表達式,就可以改寫爲
val l = r?.length ?: -1
?: 左側表達式非空,?: (elvis 操作符)就返回其左側表達式,否則返回右側表達式
注意
throw 和 return 在 Kotlin 中都是表達式,
所以,右側的表達式也支持throw和return
範例
fun foo(node: Node): String? {
val parent = node.getParent() ?: return null
val name = node.getName() ?: throw IllegalArgumentException("name expected")
// ……
}
!! 操作符
如果你想要一個 NPE,你必須顯式要求它
就用到!!操作符
範例
val l = b!!.length
b爲null的話,就會強制拋出NPE
安全的類型轉換
對象不是目標類型,常規類型轉換可能會導致 ClassCastException。
可以選擇as?
進行安全類型轉換
val aInt: Int? = a as? Int
可空類型的集合
你有一個可空類型元素的集合,但你想要過濾非空元素
可以使用 filterNotNull
範例
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()
小結
① NPE從哪裏來。
這個問題從 NPE的來源中可以知道了
② Kotlin從哪幾個方面解決了NPE
- 傳統手段:
用條件判斷if…else處理 - 鏈式調用的處理:
用?.操作符,一旦發現?.操作符 左側爲null,就立即返回null。
包括1️⃣ 終止 繼續的鏈式調用;2️⃣若這行代碼有賦值,跳過右側表達式的求值,跳過賦值 - 支持強制拋出NPE
用!!操作符,碰到NPE,也可以繼續拋出 - 額外_安全的類型轉換
用as?操作符可以進行安全的類型轉換,轉換失敗就返回null
③ Kotlin到底解決了什麼?
Kotlin的NPE方案,的確解決了很多麻煩地方。但目前來,其實,如果你不怕麻煩,你可以無限的編寫各種if…else來處理。
一個非常富有耐心的程序員可以手動解決NPE。各種判斷,各種判斷,往死裏寫。
但這太累了。所以,至少我不會這麼幹。總是挑一些沒把握的地方去寫。
Kotlin提供了什麼呢?
一種快捷處理NPE的方式。它並沒有完全規避NPE。但它改變了NPE處理的方式
- 默認 強制非Null;
- 解決鏈式調用的Null判斷痛點;
- 簡化if…else處理邏輯的編寫工作量;
- 用!!操作符強調NPE的處理意識;
- 在跨平臺的互操作方面,提供了安全的類型轉換。
5招就改變了 程序員對NPE的關注程度,關注層級不一樣,自然就不容易犯錯了;減少了編碼的工作量,寫起來不累就不會產生牴觸。
④ 在什麼時候,Kotlin的NPE解決方案是失靈的?
可以說,Kotlin的5招 cover了很多場景。
最容易出問題的當屬 跨平臺的互操作。比如java與kotlin。
java和kotlin是有互操作性。所以,需要留意類型轉換是否有進行,這需要人爲的去控制。
組件化、模塊化、AOP思想,在合理的框架下,控制Kotlin接入的值,處理不當就容易出問題,這時就會失靈。