【Kotlin学习之旅】Kotlin的null安全

一、简介

null安全可以说是Kotlin语言对Java的重大改进之一,这样可以避免Java变成时令人恐惧的NullPointerExcept(简称NPE)。

Kotlin 类型系统的设计目标就是希望消除代码中 null 引用带来的危险。

Kotlin 的类型系统致力于从我们的代码中消除 NullPointerException. 只有以下情况可能导致 NPE:

  1. 明确调用 throw NullPointerException()
  2. 使用 !! 操作符, 详情见后文使用 !! 操作符, 详情见后文
  3. 外部的 Java 代码导致这个异常外部的 Java 代码导致这个异常
  4. 初始化过程中存在某些数据不一致 (在构造器中使用了未初始化的 this)初始化过程中存在某些数据不一致 (在构造器中使用了未初始化的 this)

下面来学习下Kotlin的null安全的相关知识点!

二、Kotlin null安全实战

2.1、非空类型和可空类型

先看下面的简单例子

fun main(args: Array<String>) {
    var str = "ouyangpeng"
    // 由于str转换为Int有可能失败,故num有可能没有值,因此不能用Int来声明sum变量
    // Error:(7, 21) Kotlin: Type mismatch: inferred type is Int? but Int was expected
    var sum1: Int = str.toIntOrNull()
    var sum2: Int? = str.toIntOrNull()
    println("sum = $sum1")
    println("sum2 = $sum2") 
}

【Kotlin学习之旅】Kotlin的null安全_第1张图片

第7行声明sum1的类型是Int,第8行声明的sum2的类型是Int?
程序中第7行代码无法通过编译,第8行代码可以通过编译。

其中 Int? 就是可空类型,这种类型的变量可接受Int值和null;
而Int类型的变量只接受Int值,不能接受null。

  • 为什么编译失败?
    由于str是一个String变量,当程序试图把String变量转换为Int值时,有可能转换成功(比如变量值是形如“123”的字符串),也有可能转换失败。转换失败时,就无法成功返回Int值,此时将会返回null,因此必须使用Int?类型的变量来存储转换结果。

  • 自动推断变量类型
    对于可能发生“值确实”的情况,编译器会自动推断该变量的类型为可空类型。比如如下的代码:

// 编译器推断 n的类型为 Int?
  var n = str.toIntOrNull()
  • Kotlin对可空类型的限制

Kotlin对可空类型进行了限制:如果不加任何处理,可控类型不允许直接调用方法、访问属性。
因此,通过可控类型与非空类型的区分,Kotlin即可在程序中避免空指针异常。例如如下代码:

    // 编译器推断 n的类型为 Int?
    var n = str.toIntOrNull()

    var aStr: String = "oyp"
    var bStr: String? = "oyp"

    // 错误,aStr不接受 null, Error:(17, 12) Kotlin: Null can not be a value of a non-null type String
    aStr = null
    bStr = null

    println(aStr.length)
    //编译不通过,Error:(22, 17) Kotlin: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
    // 可空类型不能直接调用方法或属性
    println(bStr.length)

【Kotlin学习之旅】Kotlin的null安全_第2张图片

上面代码定义了aStr 和 bStr两个变量,
其中aStr是非空的String类型,因此aStr变量不允许赋值为null;
而bStr是可空类型,bStr变量允许被赋值为null。

因此程序可以调用aStr的方法或熟悉,但aStr变量的值不可能为null,因此可以避免NPE的出现。
而由于Kotlin对可空类型的限制:如果不加任何处理,可控类型不允许直接调用方法、访问属性。 因此程序不能直接调用bStr的方法或属性,这样也可以避免NPE的出现。


2.2 先判断后使用

可空类型的变量不允许直接调用方法或熟悉,单可以先判断该变量不为null,然后再调用该变量的方法或熟悉。如下代码所示:

    // 先判断后使用
    var b: String? = "oyp"
    // 先判断b 不为空,然后再访问b的属性
    var len = if (b != null) b.length else -1
    println("b的长度为 $len")

    b = null
    if (b != null && b.length > 0){
        println("b的长度为 ${b.length}")
    } else {
        println("b为空字符串")
    }

【Kotlin学习之旅】Kotlin的null安全_第3张图片

上面程序定义了String?(可控类型)的变量b,因此程序不能直接调用b的方法或熟悉。Kotlin要求程序先判断b不为null,然后在该条件下调用b的方法或属性。

  • 关于Boolean?类型的使用

因为 Boolean? 类型,可以接受 true false null 三个值, 因此 对Boolean? 类型变量判断时,要显示与 true false 的值做比较

    // 因为 Boolean? 类型,可以接受  true false null 三个值
    // 因此 对Boolean? 类型变量判断时,要显示与 true false 的值做比较
    var bParam: Boolean? = null
    if (bParam == true) {
        println("为真")
    }
    // 下面的代码 会报错:Error:(47, 9) Kotlin: Type mismatch: inferred type is Boolean? but Boolean was expected
    if (bParam) {
        println("为真")
    }

【Kotlin学习之旅】Kotlin的null安全_第4张图片

上面代码,下面的if(bParam) 直接会导致编译器报错 Type mismatch: inferred type is Boolean? but Boolean was expected。这是因为Kotlin的if条件必须为Boolean类型,而Boolean?与Boolean本质上是两种不同的类型,因此编译器会报错。

2.3 安全调用

先看下简单的例子,使用 “?.” 安全调用来访问可空类型的属性

fun main(args: Array) {

    // 安全调用  如果 b 不等于null,则返回b的长度,如果b为null,则返回null,不会导致NPE
    var b: String? = "oyp"
    println(b?.length)  // 3
    b = null
    println(b?.length)  // null
}

【Kotlin学习之旅】Kotlin的null安全_第5张图片

上面的程序中,变量b的类型为String? (可空类型),因此程序使用 “?.” 安全调用来访问b的length属性,当b为null的时候,也不会引发NPE,而是返回null。

其实翻译成普通的语句就是

    if (b == null) {
        println(null)
    } else {
        println(b.length)
    }

如果不使用安全调用的话,会报错 Error:(10, 14) Kotlin: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
【Kotlin学习之旅】Kotlin的null安全_第6张图片

  • 安全调用支持链式调用
user?.dog?.name

上面代码表示安全获取user的dog的name的属性值,如果user或者user.dog 为null,整个表达式将返回null。

  • 与let全局函数结合使用
    安全调用还可以与let全局函数结合使用,如下所示
var myArr: Array<String?> = arrayOf("oyp", null, "csdn", "xtc")
    for (item in myArr) {
        // 只有当item元素不为null才会执行的let函数
        item?.let { println(it) }
        //oyp
        //csdn
        //xtc
    }

【Kotlin学习之旅】Kotlin的null安全_第7张图片

上面代码使用安全调用来调用let函数,这样只有当item元素不为null才会执行let函数。

2.4 Elvis运算

Elvis运算也是一个小技巧,其实就是if else的简化写法。

fun main(args: Array<String>) {

    var b: String? = "oyp"
    var len1 = if (b != null) b.length else -1
    println(len1)

    b = null
    //使用 Elvis运算符
    //若第一个运算数不为null,运算结果就是第一个运算数;
    //若第一个运算数为null,运算结果就是第二个运算数。
    var len2 = b?.length ?: -1
    println(len2)
}

【Kotlin学习之旅】Kotlin的null安全_第8张图片

上面代码第6行使用了传统的if分支来进行判断,当b不为null时返回b.length,否则返回-1;
第13行代码使用了 “?:"运算符,该运算符就是Elvis,它的含义是:如果“?:"左边表达式不为null,则返回左边表达式的值,否则返回“?:"右边表达式的值。

使用 Elvis运算符
若第一个运算数不为null,运算结果就是第一个运算数;
若第一个运算数为null,运算结果就是第二个运算数。

由此可见,“?:”其实就是if分支的简化写法。

扩展一下:由于Kotlin的return和throw都属于表达式,因此它们也可以用在Elvis运算符的右边。

 val parent = user.getParent() ?: return null
 val name = user.getName() ?: throw IllegalArgumentException ("name expected")

2.5 强制调用

强制调用是为NPE“爱好者”准备的,比如你想不管变量是否为null,程序都直接调用该属性的方法或属性。Kotlin也为这种用法提供了支持,用“!!.”即可强制调用可空变量的方法或属性,这样强制调用可能引发NPE。

fun main(args: Array<String>) {
    //安全调用 ?.
    //强制调用 !!.

    //用 !! 即可强制调用可空变量的方法或属性 这样强制调用可能引发NPE
    var b: String? = "oyp"
    println(b!!.length)  // 3
    b = null
    println(b!!.length)  // 引发 kotlin.KotlinNullPointerException 空指针异常

    var myArr: Array<String?> = arrayOf("oyp", null, "csdn", "xtc")
    for (item in myArr) {
        // 只有当item元素不为null才会执行的let函数
        item!!.let { println(it) }
        //oyp
        //csdn
        //xtc
    }
}

【Kotlin学习之旅】Kotlin的null安全_第9张图片

上面代码,将 安全调用 ?. 改为 强制调用 !!. 当 b为null的时候,b!!.length就会引发NPE。
let函数里面的,不管item元素是否为null,强制调用的话也会导致NPE。


作者:欧阳鹏 欢迎转载,与人分享是进步的源泉!
转载请保留原文地址:https://blog.csdn.net/qq446282412/article/details/85209203
☞ 本人QQ: 3024665621
☞ QQ交流群: 123133153
☞ github.com/ouyangpeng
[email protected]
如果本文对您有所帮助,欢迎您扫码下图所示的支付宝和微信支付二维码对本文进行打赏。

【Kotlin学习之旅】Kotlin的null安全_第10张图片

你可能感兴趣的:(Kotlin)