前言:
空指针异常是Android系统上崩溃率非常非常高的异常类型,主要是因为空指针是一种不受编程语言检查的运行时异常,只能由程序员主动通过逻辑判断避免,但即使是最出色的程序员,也不可能将所有潜在的空指针异常全部考虑到。然而,kotlin却非常科学地解决了这个问题。它利用编译时判空检查机制几乎杜绝了空指针异常。
1. 可空类型系统
下面我们先来看一段代码:
fun doStudy(study:Study){
study.readBookd()
study.doHomework()
}
上述代码会有空指针风险吗?答案是没有。因为kotlin默认所有的参数和变量都不可为空,如果你尝试向doStudy()方法传入一个null参数,则会直接编译报错,因为Kotlin将空指针异常检查提前到了编译期。
那如果我们的业务逻辑就是需要某个变量或者参数为空怎么办呢?kotlin为我们提供了另一套可空的系统类型,只不过在使用这种类型时,我们需要在编译期就将所有潜在的空指针异常都处理掉,否则无法通过编译。
何为可为空的系统类型?就是在类名后面加上一个问号。比如String表示不可为空的字符串,而String?就表示可为空的字符串。现在回到刚才的例子:
fun main(){
doStudy(null)
}
fun doStudy(study:Study?){
study.readBookd()
study.doHomework()
}
这时候调用doStudy(null)就不会提示错误了,但你会发现doStudy()中调用readBookd()和doHomework()时“.”下面出现了红色下划线错误提示,原因你应该也猜到了,我们将原来的不可空类型参数改为了可空类型参数,若传入null就会出现空指针,这种情况kotlin是不允许编译通过的。有人说加个判空处理就可以了:
fun doStudy(study:Study?){
if(study!=null){
study.readBookd()
study.doHomework()
}
}
确实可以,但如果每个地方都使用if判断语句,会让代码变得啰嗦,为此kotlin提供了一系列的辅助工具,使开发者可以更轻松地进行判空处理。
2. 判空辅助工具
2.1 ?.操作符
?.这个操作符跟在对象后面,意思是,当对象不为空时调用操作符后面的代码,如果对象为空则什么也不做。用这个操作符改造一下之前的代码:
fun doStudy(study:Study?){
study?.readBookd()
study?.doHomework()
}
去除了if判空语句,简化代码同时也规避了空指针风险。
2.2 ?:操作符
这个操作符左右两边都接收一个表达式,如果左边的表达式结果不为空就返回左边的结果,否则返回右边的表达式结果。例如:
//1)传统写法
fun getTextLength(text:String?){
if(text!=null){
return text.length
}
return 0
}
//2)使用操作符之后的简化写法
fun getTextLength(text:String?)=text?.length ?:0
两种方式一对比,简便性高下立判。
2.3 非空断言工具 !!
不过有时候kotlin的空指针检查机制也并非总是那么智能,我们来看一段代码:
var content:String?="hello"
fun main(){
if(content!=null){
printUpperCase(content)
}
}
fun printUpperCase(){
val upperCase=content.toUpperCase()
print(upperCase)
}
从逻辑上看,这段代码并没有什么问题,但编译却无法通过,因为printUpperCase()函数并不知道外部已经对content变量进行了判空,所以在调用printUpperCase()的时候,认为存在空指针风险,所以无法编译通过。
这种情况下,如果我们想要通过编译,可以使用非空断言工具,使用方法是在对象的后面加上!!,意在告诉kotlin,我非常确信这里的对象不会为空,如果出现为空,你可以直接抛出空指针异常。
fun printUpperCase(){
val upperCase=content!!.toUpperCase()
print(upperCase)
}
2.4 let函数
let函数属于kotlin中的标准函数,它提供了函数式API的编程接口,并将原始调用对象作为参数传递到Lambda表达式中。let函数配合?.操作符可以在空指针检查的时候起到很大的作用。让我们通过let函数以及Lambda简化一下前面doStudy的例子:
fun doStudy(study:Study?){
study?.let {
it.readBooks()
it.doHomeWork()
}
}
是不是又再一次简化了原来的代码。另外,let函数可以处理全局变量的判空问题。