空指针检查
Kotlin 默认所有的参数和变量都不可为空。
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) {
a
} else {
b
}
可以简化为:
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 {
it.readBooks()
it.doHomework()
}
}