1、可空类型系统
Kotlin在编译时判空检查的机制几乎杜绝了空指针异常,虽然编译时判空机制有时候会导致代码变得比较难写,但是Kotlin提供了一系列辅助工具,让我们能轻松处理各种判空情况。
先看下面这个方法
fun doStudy(study : Study){
study.doHomeWork()
}
Kotlin默认所有的参数和变量
都不可为空,所以这里传入的study
一定不为空,如果你尝试向doStudy()
方法中传入null

可以看到,Kotlin将空指针异常提前到了编译期间,如果我们的程序存在空指针异常,那么在编译时就报错。那如果我们业务逻辑需要变量为空,该怎么办呢?很简单,直接在类名的后面加上
?
即可
可以看到在调用
doStudy()
方法时可以传入null
了,但是使用study
调用方法doHomeWork()
方法时却又编译失败了,其实很简单由于我们在传入的参数可能为空了,所以在调用方法时就可能会有空指针的风险。此时,我们就需要增加一个判断了。
fun doStudy(study: Study?) {
if (study != null)
study.doHomeWork()
}
但是在编译时期就处理掉所有的空指针异常,通常需要编写很多额外的代码检查才行,如果每处检查代码都是用if
判断就比较啰嗦,而且if判断还处理不了全局变量判空的问题,为此Kotlin提供了一些列的辅助工具。
2、判空辅助工具
2.1、?.操作符
?.操作符表示不为空则调用相应的方法,如果为空则什么都不做。
将有if判断的代码简化如下:
fun doStudy(study: Study?) {
if (study != null)
study.doHomeWork()
//使用?.替代if判断
study?.doHomeWork()
}
2.2、?:操作符
?:操作符两边都接收一个表达式,如果左边表达式不为空则返回左边表达式,否则就返回右边的表达式结果。
val c = if (a != null) {
a
} else {
b
}
//使用?:简化为
val c = a ?: b
接下来我们通过一个具体的例子来结合?.
和?:
来加深其理解,比如我们编写一个函数用来获取文本的长度。传统的写法如下:
fun getTextLength(text: String?): Int {
if (text != null)
return text.length
else
return 0
}
我们可以使用辅助操作符让它变得更简单
fun getTextLength(text: String?) = text?.length ?: 0
text?.lenght
在text为null时是不调用text.length
方法的,其返回值为null,而text?.length
作为?:
左边的表达式在text为null时其值为null,所以返回0,在左边的表达式不为null时返回左边表达式的值即text.lenght
的值。
2.3、!! 非空断言工具
不过Kotlin的空指针检查机制并不是完全靠谱,有时候我们在逻辑上已经将空指针异常处理了,但是编译依然失败,看下面的例子。
val content: String? = "hello"
fun main(){
if(content!=null)
printUpperCase()
}
fun printUpperCase(){
println(content.toUpperCase())
}
我们定义的可为空的全局变量content
,然后在main()
中做了非空的判断,但是在printUpperCase()
中调用content.toUpperCase()
还认为这里存在空指针风险,所以编译失败。
在这种情况,如果我们想强行编译通过,可以使用非空断言工具,在对象后面加上!!
。
fun printUpperCase() {
println(content!!.toUpperCase())
}
注意:非空断言是一个有风险的操作 ,意在告诉Kotlin这里肯定不为空,所以你不用来做空指针检查了,如果出现问题直接抛出空指针异常。
2.4、let函数
let
不是一个操作符也不是一个关键字,而是一个标准的函数,这个函数提供了函数式API的编程接口,并将原始对象作为参数传递到Lambda表达式中,实例如下:
obj.let { obj2->
//编写具体的逻辑
}
可以看到obj调用let函数时,代码逻辑会立即执行并将obj对象本身作为参数传递到Lambda表达式中,这里命名为obj2只是为了防止重名,其实obj2就是obj对象。
下面我们看下函数let和空指针检查的关系。先看下面的代码:
fun doStudy(study: Study?) {
study?.doHomeWork()
study?.readBook()
}
将其转换成Java代码
fun doStudy(study: Study?) {
if(study!=null)
study.doHomeWork()
if(study!=null)
study.readBook()
}
可以看到我们对study
做了两次判断,其实我们只需要判断一次就行了,这里就用到了let
函数了
fun doStudy(study: Study?) {
study?.let { stu ->
stu.doHomeWork()
stu.readBook()
}
}
study
不为null时会调用let
函数,然后将study
传递到Lambda表达式中,此时study
肯定不为空了就能放心使用了。
还记得Lambda表达式的语法特性吗?当只要一个参数时可以不用声明参数及类型并用it代替。
fun doStudy(study: Study?) {
study?.let {
it.doHomeWork()
it.readBook()
}
}
let函数可以处理全局变量的判空处理,但是if判断是无法对全局变量进行判空

从上图可以看出,报错的原因呢是:study是可变属性,此时可能已经被更改(被其他线程修改),如果生命的全局变量是用
val
修饰的是没问题的。
网友评论