• Kotlin语法学习(四)_空指针检查


    空指针检查

    • 空指针异常是一种不受编程语言检查的运行时异常,只能由程序员主动通过逻辑判断来避免,所以空指针异常往往比较容易出这个问题

    可空类型系统

    • 在Kotlin当中利用了编译时判空检查的机制几乎杜绝了空指针异常,Kotlin提供了一些列的工具,让我们能够轻松处理各种判空情况
    fun doStudy(study: Study) {
        study.readBooks()
        study.doHomeWork()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 上面这个代码如果使用Java语言来进行编写的话,当我们参数传递为null,那么下面调用两个方法就会爆出空指针异常,所以在调用方法之前应该检查一下参数是否为空,这样的话就不会出现空指针异常
    fun doStudy(study: Study) {
        if(study != null) {
            study.readBooks()
            study.doHomeWork()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 但是实际上,在Kotlin当中,根本不用if来进行判断study参数是否为空,因为在Kotlin语法中,默认所有的参数和变量都是不可以为空的,所以这里传入的Study参数一定不会为空,我们可以放心的调用它的任何函数.
    • 但是如果尝试向doStudy函数中传递一个空的Study参数,编译器就会提示一个如下的错误出现,也就是说,Kotlin将空指针的检查提前到了编译阶.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3qqhGR9Z-1669110110989)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221122160531510.png)]

    • 但是这个的前提是,Kotlin当中的所有的参数和变量都默认不为空,但是当我们的业务逻辑要求某个参数就是为空该怎么办呢?
    • 不用担心在Kotlin当中为我们提供了另外一套可以为空的系统,只不过就是在使用可以为空的系统的时候,我们需要在编译阶段就将所有潜在空指针异常的地方处理掉,否者代码无法编译通过
    • 所谓可以为空的系统激素hi在类名的后面加上一个?号,比如Int表示不可以为空的整形,Int?表示一个可以为空的整形
    • 回到刚刚的doStudy()函数,如果我们希望传入的参数的可以为空,那么就将Study改成Study?
    fun main() {
        doStudy(null)
    }
    fun doStudy(study: Study?) {
        study.readBooks()
        study.doHomeWork()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5skrv8Pv-1669110110991)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221122161312716.png)]

    • 可以看到调用doStudy()函数传入null的时候,不会再提示上面的错误了,意思就是现在Study这个参数可以为空了,但是发现在下面调用study参数的两个方法的时候,发生了报错
    • 由于我们将参数改成了可空的Study?类型,此时调用readBooks()和doHomeWork()方法的时候都有可能会造成空指针,因此在这种情况下Kotlin不允许编译通过
    • 改进代码就是
    fun doStudy(study: Study?) {
        if (study != null) {
            study.readBooks()
            study.doHomeWork()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 为了能够在编译时期就处理掉所有的空指针异常,通常需要编写很多的额外代码才行,如果每处都是用if来进行判断,就会比较啰嗦,而且if语句还是处理不了全局变量的判空问题
    • 为此Koltin提供了一系列的辅助工具,使开发者能够更轻松地进行空指针处理.

    判空辅助工具

    • 首先是最常用的?.操作符,这个操作符的作用是,当对象不为空时正常调用,当对象为空的时候什么也不干.
    • 了解了?.操作符之后,doStudy()函数进行优化,代码如下
    fun doStudy(study: Study?) {
        study?.doHomeWork()
        study?.readBooks()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 还有一个非常常用的?:操作符,这个操作符的左右两边都接受一个表达式,当左边表达式不为空的时候,就输出左边表达式的值,否者就输出右边表达式的值
    • 编写一段函数来输出一段文本的长度
    fun getTextLength(text: String?) = text?.length ?: 0
    
    • 1
    • Kotlin的空指针检查机制也不是总是那么智能,有时候我们从逻辑上将空指针异常处理了,但是Kotlin的编译器并不知道,这个时候他还是会编译失败
    val content: String? = "hello"
    
    fun main() {
        if(content != null) {
            printUpperCase()
        }
    }
    fun printUpperCase() {
        val upperCase= content.toUpperCase()
        println(upperCase)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 在上述的代码中定义了一个全局变量content,在main函数当中我们在调用printUpperCase()函数之前就已经判断了content是否为空,所以在printUpperCase()函数中content不可能为空
    • 但是很遗憾的是,这段代码一定是无法运行的,因为printUpperCase()函数并不知道外部已经对content变量进行非空检查,在调用toUpperCase()函数的时候,不知道外部已经将content变量进行了非空检查,所以content在调用toUpperCase()函数的时候还是会存在空指针异常.
    • 在这种情况下,如果我们想要强行的通过编译,可以使用非空断言工具,写法是在对象的后面加上!!操作符
    fun printUpperCase() {
        val upperCase = content!!.toUpperCase()
        println(upperCase)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 这是一种有风险的写法,意在告诉Kotlin,我非常确信这里的对象不会为空,所以你就不用来帮我做空指针的检查了,如果出现空指针问题,你抛出空指针异常,后果由我来进行承担
    • 所以这种写法是存在潜在的空指针问题的,如果有更好的写法不建议这样使用
    • let工具.let既不是操作符,也不是关键字.而是一个函数.这个函数提供了函数式API接口,并将原始调用对象作为参数传递到Lambda表达式当中
    obj.let { obj2 ->
        //编写具体的业务逻辑
    }
    
    • 1
    • 2
    • 3
    • let结合?.操作符将doStudy()方法进行优化
    fun doStudy(study: Study?) {
        study?.let {stu ->
              stu.readBooks()
              stu.doHomeWork()      
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • ?.操作符表示对象对空的时候什么都不做,对象不为空的时候就调用let函数,而let函数会将study对象本身作为参数传递刀Lambda表达式当中,此时study对象肯定就不为空了,我们就可以放心的任意调用他的方法了
    • Lambda表达式有一个特性就是说,当Lambda表达式的参数裂掉只有一个参数的时候,可以不声明参数的名字,直接使用it关键字来进行代替即可
    fun doStudy(study: Study?) {
        study?.let {
            it.readBooks()
            it.doHomeWork()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • let函数可以处理全局变量的判空问题,而if语句无法做到这一点,比如我们将doStudy()函数中的参数变成一个全局变量,使用let函数仍然可以正常工作,但是使用if判断语句会提示报错

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CmyVFq1R-1669110110992)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221122173559106.png)]

    • 之所以在这个地方会报错是因为全局变量的值,随时有可能会被其他线程所修改,即使做了判空处理,任然无法保证study变量没有空指针风险,这一点也体现出了let函数的优势.
  • 相关阅读:
    什么是 RENGA NFT?
    霍尔电流传感器在UPS蓄电池浮充电流远程监测方案的应用
    JS 这次真的可以禁止常量修改了!
    SpringBoot整合Redis
    Python:实现fibonacci斐波那契算法(附完整源码)
    5.2 加载矢量图层(delimitedtext,spatialite,wfs,memory)
    C++:类和对象(下)
    大数据存储ClickHouse
    基于标准反向传播算法的改进BP神经网络算法(Matlab代码实现)
    简单评分程序(面向对象程序设计及C++)
  • 原文地址:https://blog.csdn.net/weixin_45809829/article/details/127987094