Osmanthus

空想具現化


  • 首页
  • 归档
  • 分类
  • 标签
  • 关于
  •   

© 2024 Homurax

UV: | PV:

Theme Typography by Makito

Proudly published with Hexo

Kotlin 基础03 - 空指针检查机制

发布于 2020-04-06 Kotlin  Kotlin 基础 

空指针检查

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()
    }
}

 上一篇: Kotlin 基础04 - Lambda、函数式编程 下一篇: Kotlin 基础02 - 类、对象、初始化 

© 2024 Homurax

UV: | PV:

Theme Typography by Makito

Proudly published with Hexo