空指针检查
fun main() { // error! Null can not be a value of a non-null type Study doStudy(null) fun doStudy(study: Study) { study.readBooks() study.doHomework()
Kotlin 将空指针异常的检查提前到了编译期,如果程序存在空指针异常的风险,那么在编译的时候会直接报错。
可空类型
Kotlin 提供了另外一套可为空的类型系统,在使用时需要在编译期就将所有潜在的空指针异常都处理掉,否则代码无法编译通过。
使用上就是在类名的后面加上一个问号。比如,
Int?
表示可为空的整形,String?
表示可为空的字符串。如果使用了 ? 而不处理潜在可能的空指针异常,不能通过编译:
fun main() { doStudy(null) fun doStudy(study: Study?) { // Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Study? study.readBooks() // Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Study? study.doHomework()
处理掉空指针异常,可以通过编译:
fun doStudy(study: Study?) { if (study != null) { study.readBooks() study.doHomework()
判空辅助工具
?.
操作符,当对象不为空时正常调用相应的方法,为空时什么也不做。对于如下代码:
if (study != null) { study.readBooks() study.doHomework()
可以转换为:
study?.readBooks() study?.doHomework()
?:
操作符,操作符左右都接收一个表达式,如果左边表达式的结果不为空就返回左边表达式的结果,否则就返回右边表达式的结果。val c = if (a != null) { } else {
可以简化为:
val c = a ?: b
一个同时使用
?.
和?:
的例子,对如下获得文本长度的函数:fun getTextLength(text: String?): Int { if (text != null) { return text.length return 0
可以简化为:
fun getTextLength(text: String?) = text?.length ?: 0
当 text 为空时,
text?.length
会返回一个 null,再借助?:
让它返回0。
!!
非空断言操作符。有时可能从逻辑上已经将空指针异常处理了,但是 Kotlin 的编译器并不能认知到,这个时候还是会编译失败,常见对于全局变量进行判断时。
var content: String? = null fun main() { if (content != null) { printUpperCase() fun printUpperCase() { // error! Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String? val toUpperCase = content.toUpperCase() println(toUpperCase)
printUpperCase()
函数并不知道外部已经对 content 变量进行了非空检查,在调用toUpperCase()
方法时,还认为这里存在空指针风险,从而无法编译通过。如果想要强行通过编译,可以使用非空断言工具,写法是在对象的后面加上
!!
fun printUpperCase() { val toUpperCase = content!!.toUpperCase() println(toUpperCase)
这是一种有风险的写法,意在告诉 Kotlin 不需要做空指针检查,如果出现问题,可以直接抛出空指针异常。
当想使用非空断言工具时,最好想想是不是有更好的实现方式。你最自信这个对象不会为空的时候,其实可能就是一个潜在空指针异常发生的时候。
辅助工具
let
不是操作符或关键字,而是一个函数。这个函数提供函数式 API 的编程接口,并将原始调用对象作为参数传递到 Lambda 表达式中,其中代码会立即执行。上面使用
?.
的代码:study?.readBooks() study?.doHomework()
实际对应的代码就是:
if (study != null) { study.readBooks() if (study != null) { study.doHomework()
每一次使用
?.
都进行了一次 if 判断。使用
let
结合?.
可以进行简化fun doStudy(study: Study?) { study?.let { stu -> { stu.readBooks() stu.doHomework()
又因为当 Lambda 表达式的参数列表只有一个参数时,可以使用
it
关键字代替fun doStudy(study: Study?) { study?.let { it.readBooks() it.doHomework()
这里 let 的使用有点类似于 Java 8 中的 Optional 。
let 函数是可以处理全局变量的判空问题的,if 无法做到这一点。
var study: Study? = null fun doStudy() { if (study != null) { // error! Smart cast to 'Study' is impossible, because 'study' is a mutable property that could have been changed by this time study.readBooks() // error! study.doHomework()
因为全局变量的值随时都有可能被其他线程所修改,即时做了判空处理,仍然无法保证 if 语句中的 study 变量没有空指针风险。
使用 let 编译通过:
var study: Study? = null fun doStudy() { study?.let {