4.1 可空类型系统
Kotlin在代码编译阶段会检查所有参数和变量是否为非空,若有空值则报错
当我们的业务逻辑需要某个参数或变量为空时,Kotlin为我们提供了一套可为空的类型系统
但在使用这套系统时,我们需要在编译前就将所有潜在的空指针异常处理掉,否则代码将无法编译通过
可为空的类型系统,就是在原来类型名后加上?,如Int表示不可为空的整型,而Int?则表示可为空的整型
对于上图,在类型名Student后加上问号,改为Student?,就能传入null参数了
但又出现了新的红线,因为此时可传入null参数,此时调用方法都可能造成空指针异常,因此Kotlin不允许在可传入null参数的方法中,不做非空判断的情况下直接调用类方法
fun doStudy(stu:Student?){
if(stu != null){//对对象stu作非空检查,这样就能避免空指针异常并通过编译了
stu.readBooks()
stu.doHomework()
}
}
为了在编译时期处理掉所有空指针异常,通常需要编写很多额外的检查代码。如果每处检查代码都使用if条件语句,会让代码比较啰嗦,而且if判断语句处理不了全局变量的判空问题。为此,Kotlin还提供了一系列判空处理的辅助工具。
4.2 判空辅助工具
4.2.1 ?.操作符
?.操作符就是当对象不为空时,正常调用相应方法;当对象为空时什么也不做,如:
if(a != null){
a.doSomething()
}
//对以上判空方法,用?.操作符可简化为:
a?.doSomething()
对上节的doStudy()函数,可简化为:
fun doStudy(stu:Student?){
stu?.doHomework()
stu?.readBook()
}
4.2.2 ?:操作符
?:操作符左右两边都接受一个表达式,若左边表达式结果不为空就返回左边表达式结果,否则返回右边表达式的结果
val c = if(a != null){a}else{b}
//对以上操作,用?:操作符可写为:
val c = a ?: b//使用方法类似于C语言的问号表达式
对于一个获取字符串长度的函数:
fun getStringLength(str:String?):Int{
if(str!=null){//使用if判断语句判空
return str.length
}
return 0
}
当我们用?.操作符和?:操作符后可写为:
fun getStringLength(str:String?)=str?.length ?: 0
4.2.3 非空断言工具
有时Kotlin代码从逻辑上已经完成了空指针异常处理,但编译器可能还是会编程失败:
var content:String?="Hello"
fun printUpperCase(){
val upperCase= content.toUpperCase()//代码无法编译通过,因为编译器认为content可能为空
println(upperCase)
}
fun main(){
if(content!=null){//但实际上我们已经对content作了非空判断
printUpperCase()
}
}
如果我们想要强行通过编译,可以使用非空断言工具!!,即在对象后加上!!
fun printUpperCase(){
val upperCase= content!!.toUpperCase()//此时编译可以通过,我们告诉编译器此处对象不为空
println(upperCase)//但这样做是有风险的,因为我们跳过了编译器在此处的非空检查
}
4.2.4 let函数
let函数提供了函数式API的编程接口,并将原始调用对象作为参数传递到Lambda表达式中:
obj.let{obj2 ->
//编写具体业务逻辑
}
这里调用了obj对象的let函数,然后Lambda表达式中的代码就会立即执行,并且该对象本身会作为参数传递到Lambda表达式中
为了防止重名,我们将参数名改为obj2,但实际上它们是同一个对象
//对于之前的doStudy()函数:
fun doStudy(stu:Student?){
stu?.doHomework()
stu?.readBook()
}
//将?.操作符翻译成if:
fun doStudy(stu:Student?){
if(stu != null){
stu.doHomework()
}
if(stu != null){
stu.readBook()
}
}//这里可以看出,?.操作符在这里的使用比较啰嗦
//如果使用?.操作符和let函数结合:
fun doStudy(stu:Student?){
study?.let{study->//?.操作符使对象为空时不作任何操作
study.readBook()//若不为空,则let函数将stu对象本身作为参数传递到Lambda表达式中
study.doHomework()//此时对象一定不为空,可以执行方法
}
}
//当Lambda表达式的参数列表中只有一个参数时,可以用it关键字指代参数名
fun doStudy(stu:Student?){
study?.let{
it.readBook()
it.doHomework()
}
}
4.2.5 特别注意
if判断语句无法对全局变量判断非空,如图
因为全局变量的值随时可能被其他线程修改,即使判空处理也无法保证if语句中的study变量没有空指针风险
而let函数可以判断全局指针是否为空