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