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") 

第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)

上面代码定义了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为空字符串")

上面程序定义了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("为真")

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

2.3 安全调用

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

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

上面的程序中,变量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?
在这里插入图片描述

  • 安全调用支持链式调用
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

上面代码使用安全调用来调用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)

上面代码第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

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

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

【CSDN 编者按】Kotlin 和 Java 是如何解决 Null 问题?本文作者分享了解决思路。原文链接:https://blog.frankel.ch/null-safety-java-vs-kotlin/未经授权,禁止转载!作者 |Nicolas Fränkel 责编 |弯月出品 | CSDN(ID:CSDNnews)在本文中,我想讨论一下 Kotlin 和 Java 是如何解决 ... 文章目录背景Kotlin 的优势1. 用于服务端开发2. 用于 JavaScript3. 开发 Android 应用第一个 Kotlin 程序分析:Kotlin 语言的特点Kotlin 语言中的注释 Kotlin 是 JetBrains 在 2011 年推出的一门全新编程语言,最早被设计运行在 JVM 上 , 使用 Kotlin 编写的程序会被编译成 Java 的字节码文件。Kotlin 可以和现在的 Java 语言包 100% 兼容, 而且 Kotlin 代码比 Java 代码更简洁、更富有表现力 这里写目录标题一、空安全相关1.1 定义一个可空类型的变量1.2 安全调用操作符——?.1.3 带let的安全调用1.4 ?:操作符1.5 非空断言操作符——!!操作符1.6 安全的类型转换——as?操作符1.7 类型检测及自动类型转换——is运算符 一、空安全相关 1.1 定义一个可空类型的变量 定义一个可空类型的变量的格式为:修饰符 变量名 : 类型? = 值,kotlin默认定义的变量不能为空 // 定义一个不可为空的变量,用var修饰的变量可以被重新赋值,用val修饰的变量则不能,但是不能 val name="Jimmy's friend" var index=name.indexOf("\'") val str=name.substring(0 until index ) println(str) Jimmy 这没啥好说的,和java差不 Kotlin 的类型系统致力于从我们的代码中消除 NullPointerException. 只有以下情况可能导致 NPE: 明确调用 throw NullPointerException()使用 !! 操作符, 详情见后文外部的 Java 代码导致这个异常初始化过程中存在某些数据不一致 (在构造器中使 null安全可以说是Kotlin语言对Java的重大改进之一,这样可以避免Java编程时令人恐惧的“NullPointerException”(简称:NPE)。但话说回来,null安全不过是各种现代语言玩剩下的东西。1 非空类型和可空类型下面先看一个简单的例子。程序清单:codes\02\2.8\NullableTest.ktfun main(args: Array&lt;String&gt;) ... 学安卓第一天 跟着 第一行代码 敲了一个取最大值的函数,在main方法里运行时提示 Type mismatch: inferred type is Int but Unit was expected 仔细看了一下,原来是方法参数后面我忘了加返回值,补上就解决了 Kotlin 类型系统的设计目标就是希望消除代码中null引用带来的危险。 Kotlin 的类型系统致力于从我们的代码中消除NullPointerException. 只有以下情况可能导致NPE: 明确调用 throw NullPointerException() 使用 !! 操作符, 详情见后文 外部的 Java 代码导致这个异常 初始化过程中存在某些数据不一致 (在构造器中使用了未初始化的 this) 在 Kotlin 中, 类型系统明确区分可以指向null的引用 (可为null引用) 与不可以指向 // @file:JvmName("Stu") 注意:必须写在 包名的外面 fun getStudentNameValueInfo(str : String) = println(str) fun main() {} /* 背后生成的代码: public final class TestKt {