【6】Kotlin基础——神奇的空指针检查系统
提示:此文章仅作为本人记录日常学习使用,若有存在错误或者不严谨得地方欢迎指正。
一、空指针系统
空指针是编程中很常见的一类异常,即使再细心的程序员也不能保证所写的代码一定不会发生空指针。在Kotlin语言中,有一套很完整的空指针预防系统,可以科学的解决空指针这一大难题。
1.1 可空类型系统
我们先来看一段很普通的Java代码:
public void doStudy(Study study) {
study.readBooks();
study.doHomework();
}
这段代码很简单,只是接收了一个Study类型的参数,并且调用了两个方法。这段代码是不是看起来不太像是“会出现问题的代码”?但我要告诉你的是,这段代码并不安全。如果我们向doStudy()方法中传入一个null参数,那么这段代码肯定会发生空指针异常。更稳妥的做法是在方法内部调用参数时对参数study进行判空处理。
public void doStudy(Study study) {
//若study不为null才执行下面的调用逻辑
if(study != null){
study.readBooks();
study.doHomework();
}
}
在Kotlin语言中,默认所有的参数和变量都不可为空。在下面的代码中,我们将之前的Java代码转化为Kotlin语言代码。如果我们尝试向doStudy()方法中传入一个null参数,编译器是会提示错误信息的。
fun main() {
//这里参数null会报错并提示不可为空
doStudy(null )
}
fun doStudy(study: Study) {
study.readBooks()
study.doHomework()
}
也就是说,Kotlin将空指针异常检查提前到了编译时期。如果我们的代码存在可能出现空指针的风险,在编译的时候就会报错。那么我们确实希望参数可以为空该怎么办呢?很简单,只需要在类名后面加上一个问号,告诉Kotlin这个参数可以为空就可以了。
例如:
Int —— 表示不可为空的整型
Int ? —— 表示可为空的整形
String —— 表示不可为空的字符串
String ? —— 表示可为空的字符串
fun main() {
//这里并不会报错,因为doStudy方法的参数可以为空
doStudy(null )
}
//study参数允许为空
fun doStudy(study: Study?) {
//由于参数study可为空,如果参数为空并且调用以下两个方法就会出现空指针,所以需要判空处理
if(study != null){
study.readBooks()
study.doHomework()
}
}
1.2 可空辅助工具
①首先我们先来学习一下 ?. 操作符。它的作用是当对象不为空时才调用相应的方法,否则什么都不做。例如以下代码就可以使用 ?. 操作符进行简化。
if (a != null) {
a.doSomething()
}
|
| simplify
|
V
a?.doSomething()
这样说明可能对你的理解有帮助:
按照我们正常的逻辑,我们最终是希望执行a.doSomething()这个方法的。但是由于我们并不确定这个a对象是否为null,所以才要去进行判空处理。Kotlin中可以在对象调用方法时通过给对象后面加上一个问号来提示判空,只有当对象不为空时才去调用这个方法。
a.doSomething() —— ——> a?.doSomething()
②接下来我们学习 ?:操作符。这个操作符左右两边都接收一个表达式(例如:A ?: B)。如果左边表达式的结果不为null,则返回左边表达式的结果。如果左边表达式的结果为null,则返回右边表达式的结果。
val c = if (a != null) {
a
} else {
b
}
我们可以使用刚刚学习的 ?:操作符 将这段代码简化成:
val c = a ?: b
比如我们想编写一个用来获取文本长度的方法,使用传统写法可以这样写:
//如果文本不为空则返回文本的长度,否则返回0
fun getTextlength(text: String?): Int {
if (text != null ) {
return text.length
}
return 0
}
我们可以将前面学习的两个操作符结合起来使用:
我们的目标是返回文本的长度,也就是text.length。若文本不为null则返回文本长度,否则我们返回0,也就是表示文本为null。那么我们可以这样写:
//若text.length不为null则返回text.length,否则返回0
fun getTextLength(text: String?) = text.length ?: 0
上面这一行代码很好的实现了我们的需求。当text.length不为null的时候返回文本长度,否则返回0。但是这里有一个问题,text也可能为null,当text为null的时候我们如果调用length字段会出现空指针异常。因此我们还需要使用 ?.操作符 对text进行判断:
//若text不为null再调用length字段
fun getTextLength(text: String?) = text?.length ?: 0
③Kotlin的空指针判断也存在一些缺陷,有时候我们可能从逻辑上已经将空指针异常处理了,但是Kotlin编译器并不知道,这个时候它就会报错。
例如以下代码:
//全局变量content
var content: String = "hello"
fun main() {
//判空操作
if (content != null) {
printUpperCase()
}
}
fun printUpperCase() {
//编译器会认为toUpperCase()有空指针风而报错,因为该方法并不知道外部已经对content变量进行了非空检查
val upperCase = content.toUpperCase()
println(upperCase)
}
如果想让这段代码强行通过编译,可以使用非断言工具。写法是 在对象的后面加上!! ,强制忽略此处的空指针检查。这相当于告诉Kotlin我非常确信这里的对象不会为null,不用帮我进行空指针检查了。如下图所示:
fun printUpperCase() {
val upperCase = content!!.toUpperCase()
println(upperCase)
}
④最后我们来学习let函数。let函数可以将调用它的对象作为参数传递到Lambda表达式中。
fun doStudy(study: Study?) {
if(study != null) {
study.readBooks()
}
if(study != null) {
study.doHomework()
}
}
|
| simplify
|
V
fun doStudy(study: Study?) {
//当study为null时什么也不做 否则就调用let函数
//let函数会将study对象作为参数传递给Lambda表达式中
study?.let { stu ->
stu.readBooks()
stu.doHomework()
}
}
|
| simplify
|
V
fun doStudy(study: Study?) {
study?.let {
it.readBooks()
it.doHomework()
}
}
let函数可以针对全局变量的判空问题进行处理,而if判断语句则无法做到这一点。例如以下代码,我们将doStudy()方法中的参数变成一个全局变量,使用let函数仍可以正常工作,但是使用if语句则会提示错误:
var study: Study = null
fun doStudy() {
if(study != null) {
//study报错
study.readBooks()
//study报错
study.doHomework()
}
}
之所以会报错是因为全局变量study的值随时都可能被其他线程修改而发生变化。即使我们做了判空处理,但仍然无法保证if语句中的study变量没有空指针风险,这就是let函数的优势。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!