3.9 空指针安全(Null-safety)
我们写代码的时候知道,在Java中NPE(NullPointerExceptions)是一件成程序员几近崩溃的事情。很多时候,虽然费尽体力脑力,仍然防不胜防。
以前,当我们不确定一个DTO类中的字段是否已初始化时,可以使用@Nullable和@NotNull注解来声明,但功能很有限。
现在好了,Kotlin在编译器级别,把你之前在Java中需要写的null check代码完成了。
但是,当我们的代码
- 显式调用
throw NullPointerException() - 使用了
!!操作符 - 调用的外部 Java 代码有NPE
- 对于初始化,有一些数据不一致(如一个未初始化的
this用于构造函数的某个地方)
也可能会发生NPE。
在Kotlin中null等同于空指针。我们来通过代码来看一下null的有趣的特性:
首先,一个非空引用不能直接赋值为null :
1. >>> var a="abc"
2. >>> a=null
3. error: null can not be a value of a non-null type String
4. a=null
5. ^
7. >>> var one=1
8. >>> one=null
9. error: null can not be a value of a non-null type Int
10. one=null
11. ^
13. >>> var arrayInts = intArrayOf(1,2,3)
14. >>> arrayInts=null
15. error: null can not be a value of a non-null type IntArray
16. arrayInts=null
17. ^
这样,我们就可以放心地调用 a 的方法或者访问它的属性,不会导致 NPE:
1. >>> val a="abc"
2. >>> a.length
3. 3
如果要允许为空,我们可以在变量的类型后面加个问号?声明一个变量为可空的:
1. >>> var a:String?="abc"
2. >>> a=null
3. >>> var one:Int?=1
4. >>> one=null
5. >>> var arrayInts:IntArray?=intArrayOf(1,2,3)
6. >>> arrayInts=null
7. >>> arrayInts
8. null
如果我们声明了一个可空String?类型变量na ,然后直接调用length属性,这将是不安全的。编译器会直接报错:
1. >>> var na:String?="abc"
2. >>> na=null
3. >>> na.length
4. error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
5. na.length
6. ^
我们使用安全调用?. 和 非空断言调用 !!.
1. >>> na?.length
2. null
3. >>> na!!.length
4. kotlin.KotlinNullPointerException
我们可以看出,代码返回了null 和 kotlin.KotlinNullPointerException。
安全调用在链式调用中很有用。在调用链中如果任意一个属性(环节)为空,这个链式调用就会安全返回 null。
如果要只对非空值执行某个操作,安全调用操作符可以与 let (以调用者的值作为参数来执行指定的函数块,并返回其结果)一起使用:
1. >>> val listWithNulls: List<String?> = listOf("A", "B",null)
2. >>> listWithNulls
3. [A, B, null]
5. >>> listWithNulls.forEach{
6. ... it?.let{println(it)}
7. ... }
8. A
9. B
本章小结
本章我们学习了Kotlin语言的基本词汇(关键字、标识符等)、句子(流程控制、表达式、操作符等)和一些基础语法。同时,学习了空指针安全、扩展函数与扩展属性等的语言特性。
我们将在下一章节中介绍Kotlin的基本类型和类型系统。
参考资料
1.<https://www.kotlincn.net/docs/reference/grammar.html>
2.<https://en.wikipedia.org/wiki/Elvis_operator>
