Kotlin: 空指针检查(补档)

*空指针异常是Android系统上崩溃率最高的异常类型。

可空类型系统

Kotlin利用编译时判空检查的机制几乎杜绝了所有空指针异常。

分析一段Java代码:

1
2
3
4
public void doStudy(Study study) {
study.readBooks();
study.doHomework();
}

如果向doStudy()方法传入一个Null参数,则会发生空指针异常,因此要在调用参数的方法之前进行一个判空处理,如下代码:

1
2
3
4
5
6
public void doStudy(Study study) {
if(study != null){
study.readBooks();
study.doHomework();
}
}

这样,就避免了此方法中的空指针错误。

如果在Kotlin中重写上述的第一段代码:

1
2
3
4
fun doStudy(study : Study) {
study.readBooks();
study.doHomework();
}

那么这段代码是没有空指针风险的,因为Kotlin默认所有的参数和变量都不可为空。Kotlin将空指针异常的检查提前到了编译时期。如果程序中存在出现空指针异常的风险,Kotlin则会在编译时报错,修正之后才能成功运行。

考虑到某些方法在设置的时候就是需要传入空值的,Kotlin提供了另一种可空类型的写法:在类名的后面加上一个“?”。Int表示不可为空的整形,而Int?就表示可为空的整形。String表示不可为空的字符串,String?则表示可为空的字符串。对于刚才的代码,如果我们希望传入的参数可以为空,那么就应该在Study的后面加上"?",代码如下:

1
2
3
4
fun doStudy(study : Study?) {
study.readBooks()
study.doHomework()
}

如上编写doStudy方法,我们就可以在调用其时向其中传入空参数:

1
2
3
fun main() {
doStudy(null)
}

此时程序便不会报错。

然而,在doStudy函数中调用参数的readBooks()和doHomework()方法时,出现了红色下划线错误提示。原因是由于参数为可空的Study?类型,此时readBooks()和doHomework()都可能出现空指针异常。因此,需要将空指针异常都处理掉:

1
2
3
4
5
6
fun doStudy(study : Study?) {
if (study != null){
study.readBooks()
study.doHomework()
}
}

如此,便可以避免空指针错误。

由于在程序中如此编写if判断语句来进行空指针的检查会比较麻烦,Kotlin提供了一系列的判空辅助工具


Kotlin中的判空辅助工具

?.操作符

含义:当对象不为空的时候调用相应的方法,当对象为空时则什么都不做

如下代码:

1
2
3
if (a != null) {
a.doSomething()
}

这段代码可以使用?.操作符简化如下:

1
a?.doSomething()

用?.操作符对doStudy()方法进行优化,代码如下:

1
2
3
4
fun doStudy(study : Study?) {
study?.readBooks()
study?.doHomework()
}

?:操作符

含义:该操作符左右两边都接收一个表达式,如果左面表达式的结果不为空就返回左面表达式的结果,否则就返回右边表达式的结果。

观察如下代码:

1
2
3
4
5
val c = if (a != null) {
a
} else {
b
}

这段代码用?:操作符可以简化为:

1
val c = a ?: b

使用?.及?:简化一段代码:

编写一个函数,用来获得一段文本的长度

1
2
3
4
5
6
fun getTextLength(text : String?) : Int {
if(text != null){
return text.length
}
return 0
}

简化后:

1
fun getTextLength(text : String?) = test?.length ?: 0 

非空断言

Kotlin的判空机制并非那么只能。在某些情况下即使已经在逻辑上避免了空指针异常,编译器仍会报错,因此需要用到非空断言工具,写法是在对象的后面加上!!,如下代码:

1
2
3
4
fun printUpperCase() {
val upperCase = Content!!.toUpperCase()
println(upperCase)
}

let工具

let是一个函数,这个函数提供了函数式API的编程接口,并将原始调用对象作为参数传递到Lambda表达式中,如下实例代码:

1
2
3
obj.let { obj2 - >
//编写具体的业务逻辑
}

这里调用了obj对象的let函数,然后Lambda表达式中的代码就会立即执行,并且这个obj对象本身还会作为参数传递到Lambda表达式中。在上述代码中,obj2和obj是同一个对象。

用let工具优化代码:

1
2
3
4
5
6
fun doStudy(study : Study?) {
study?.let {
it.readBooks()
it.doHomework()
}
}

注:let函数是可以处理全局变量的判空问题的,而if判断语句则无法做到这一点。