5招鮮-Kotlin這樣處理NPE

在這裏插入圖片描述

前言

相傳NPE值十億美金。哇~那是好多?

Kotlin爲何這麼??號稱解決了NPE呢?
① NPE從哪裏來
② Kotlin從哪幾個方面解決了NPE
③ Kotlin到底解決了什麼?
④ 在什麼時候,Kotlin的NPE解決方案是失靈的?


NPE的來源

有四個來源

  • 顯式調用 throw NullPointerException()
  • 使用了 !! 操作符
    • 這是Kotlin提供的用戶可強制拋出NPE的操作符
    • 使用了!!只要碰到NPE就會如期拋出
  • 有些數據在初始化時不一致
    • this泄漏
      未初始化的this被傳遞,並用於其他地方
    • 調用了一個 超類中的open成員,並在 派生類中使用了 該open成員的未初始化 狀態
  • 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

看個例子
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接入的值,處理不當就容易出問題,這時就會失靈。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章