可空性是kotlin类型系统帮助你避免NullPointerException错误的特性。作为搬砖的我们会经常遇到过这样的问题,如果控制不好就很容易出现空指针的问题,然而解决起来也很容易,可是出现了就是毁灭的性的行为,那怎么去更好的避免呢?Java里面大家可能会想到就是直接判断非空即可,可是这样我们会发现会有很多的if语句以及判空逻辑,当然这只是在你还在用Java开发的基础之上而言,好在Java8之后出现了一个非常好用的库(Optional),可以帮助我们很好的处理空的问题。不过我们目前考虑的是急于kotlin开发的过程,所以我们研究的是kotlin可以带给我们怎么样的便利在这一方面。

kotlin解决这类问题的方法是把运行时的错误转变为编译期的错误。通过支持作为类型系统的一部分的可空性,编译器可以在编译期就能发现很多潜在的错误,从未减少运行时爆出异常的可能性。

本篇微博会讨论kotlin中的可控类型:kotlin怎样表示允许为null的值,以及kotlin提供的处理这些值得工具。除此之外,我们还要讨论混合使用kotlin和Java代码时关于可控类型的细节。

首先我们先看如下代码:

int strLen(String s){
    return s.length();
}

这个函数是安全的么?很显然是不安全的,如果s为null那么我们的代码就会报空指针的错误了。那我们可以在用之前加上对null的检查,就可以避免这个问题了,可是这只是在运行期才会发现的,编译器在编译期是发现不了的。

同样的代码如果用kotlin写如下:

fun strLen(s: String) = s.length

那么我们在调用的时候发现:

Kotlin 的类型系统 可空性-LMLPHP

编译期会友好的告诉我们这里是不允许放null值的。

当然如果想要让我们编译器通过也很简单,我们只需要在参数类型后面加上?符号就可以了,如下:

fun strLen(s: String?) = s?.length

? 的意思是说我们允许这个参数为空,但是放到Java里面,我们传null编译器是不会发现这个安全隐患的。

所以这就是编译器的强制措施。

重申一下,没有问号的类型表示这种类型的变量不能存储null引用。这说明所有常见类型默认都是非空的,除非显示的把它标记为可空。

然后一旦我们声明这个参数是可以为空的时候,那么我们就会发现这个参数的调用方法就会受到限制,如下:

告诉我们有两种方式解决这个问题,要么使用?.这个操作符或者!!这个操作符,那么首先我们先来说一下?.这个操作符号,为什么我们在s后面加个?编译器就通过了编译了呢?

?.操作符的使用

这个操作符作为kotlin的弹药库中最有效的一个操作符了,哈哈,非常强大。

它允许你把一次null检查和一次方法调用混合在一起并成一个操作。例如,表达式s?.toUpperCase(),等同于以前的繁琐的写法,if(s != null) s.toUpperCase else null。

换句话说,当我们调用空值得时候这个代码会正常执行。如果为null,那么这个调用就不会发生,而整个表达式的值为null。

?:操作符的使用

这个操作符被称为Elvis运算符,接受两个运算数,如果第一个运算数不为null,运算结果就会返回第一个运算数;如果第一个运算数为null,运算结果就是第二个运算数。

fun strLen(s: String?){
    val t: String = s ?: ""
}

使用如上。

它也可以结合之前的操作符一起使用, s?.length?:0

如果s为null则返回null并且最终结果会返回0,如果s不为null那么会返回长度最终会返回长度值。

在kotlin中我们有一种场景下Elvis运算符会特别顺手,想return和throw这样的操作其实是表达式,因此他们可以写在Elvis运算符的右边。这种情况下,如果运算符左边的值为null则会返回一个值或者异常,否则返回运算符昨天数值。

fun printShoppingLabel(person: Person){
    val address = person.company?.address
        ?: throw IllegalArgumentException("no address")
    with(address){
        print(streetAdress)
        print("$zipCode $city $ country")
    }
}

如上的代码解读:如果company为空则会返回null值然后会抛出异常,否则直接返回地址,并且执行with操作符,打印地址信息。

安全转换运算符:”as?“

as是用来类型转换的操作符,相当于Java里面我们强转的功能。如果被换下的值不是我们所要转换的类型,就会抛出异常类型转换异常。当然我们也可以使用is来进行安全的判断,那么kotlin也有最优雅的解决方法,嘿嘿😜

as?就可以做到这点,foo as? Type ,解读:如果foo is Type 则会返回相应的类型,否则会返回null。

一种常见的模式是把安全转换和Elvis运算符结合使用。例如使用equals的时候非常方便。

class Person1(val firstName:String, val lastName:String){
    override fun equals(other: Any?): Boolean {
        val otherPerson = other as? Person1 ?: return false

        return otherPerson.firstName == firstName && otherPerson.lastName == lastName
    }

    override fun hashCode(): Int {
        return firstName.hashCode() * 37 + lastName.hashCode()
    }
}

使用这种模式,可以非常容易的检查实参是否是适当的类型,转换它,,并在它类型不正确的时候返回false,而且这些操作全部在同一个表达式之中。

非空断言”!!“

非空断言是kotlin提供给你的最简单直率的处理可控类型值得工具。他使用双叹号来表示,可以把任何数值准换成非空类型。如果对null值进行非空断言,则会抛出异常。

如下:

fun ignoreNulls(s:String?){
    val sNotNull:String = s!!
    print(sNotNull.length)
}

当我们的s为null的时候,我们的程序会报错,空指针的错误,但是细心的我们会发现,报错的位置是在非空断言那里,而不是试图使用这个数值的那个地方。本质上你是在告诉服务器,不管这个数值是否为null,我都要去执行,很粗暴有木有哈哈,就是如果错了我已经准备好接受这个异常了。

但是确实也是存在使用这种非空断言的时候,当我们在一个函数里面进行非空的检查之后,而在另一个函数里面我们要使用到这个数值,那我们不想进行重复的检查,我们也是知道这个函数执行之前肯定会进行另一个函数进行检查的,所以我们就可以使用非空断言,我个人觉得就是我们知道这个数值肯定非空但是编译器不知道,所以我们就不用再一次进行非空校验了,强制使用这个数值即可,我是这么理解的。

切记还有一个很重要的一点就是不要把多个非空断言集中在一个表达是里面使用,因为我们无法确定哪一个非空断言出现了问题!!!

05-16 08:23