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接入的值,处理不当就容易出问题,这时就会失灵。

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