【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 ? —— 表示可为空的整形
其他类型同理。

fun main() {
    //这里并不会报错,因为doStudy方法的参数可以为空
    doStudy(null )
}

//doStudy()的参数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函数的优势。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值