Kotlin 可空类型系统的学习

引言

什么是类型?为啥变量拥有类型?

“类型就是数据的分类… 决定了该类型可能的值和可以完成的操作。”

那么套用这个定义来分析一下NULL值在类型系统的地位。

比如在java中null是一种数值,但是当一个引用为null的时候,并不可以在这个引用调用该类型的任何方法,这不就意味着java的类型系统在值为NULL的时候不能很好的工作,
甚至当你使用instanceof运算符的时候,它的类型都是NULL。

既然如此,为什么不把null当做一种类型呢?

所以,Kotlin就将NULL值当做一个类型来支持。这样带来的优点就是,将NullPointerException异常从一个运行时候可能出现的错误,变成一个在编译时候就会暴露的错误。

在性能方面,值得一提的是,Kotlin中的空类型并不是非空类型的包装。所有的检查都发生在编译器。也就意味着不会在运行时带来额外的开销。

Null类型可执行的操作

最重要的就是进行类型转换。
也就是通过 !=null 来将null类型转换成非空类型。

当然可以通过定义扩展函数来对null类型添加可调用的方法。

例如

fun String?.verifyNull() {
    if (this == null) {
        println("input is null")
    } else {
        println("input is $this")
    }
}

fun main() {
    var input : String?

    input = null
    input.verifyNull()

    input = "abc"
    input.verifyNull()
}

我们就为String 添加了一个对于null值的调用。

回到java,在java中,this一定是非空的,毕竟我们是无法在一个空引用上调用方法的,因为java方法的调用是通过实例进行分发的。
但是在Kotlin中,this可以是Null的,毕竟扩展函数其实是将引用当做一个参数传入了函数中,参数可以为空,所以this自然也可以为空。

安全调用运算符?.

让我们来进行第一步的简化。
if (this != null) { ... }
如果为了支持空类型来引入这么多多余的代码有点没必要,
那么不如用一个符号来代替上述的含义。
?. 意味着 把一次null检查和一次方法调用合并在成一个操作。
即 如果这个值不为空就调用,为空就不调用并返回null。
当然不只是调用方法,还可以访问属性,毕竟访问属性也是方法调用,调用get()嘛。

=> 进一步来说,因为?.是有返回值的,所以在你的对象中拥有的可空的属性也拥有可空属性的时候就可以进行链式多个安全调用。
A ?. a ?. b ?. c()

Elvis 运算符 ?:

又叫做null合并运算符。
它的接受两个运算符,当第一个运算数为Null,运算结果就是第二个运算符;当第一个运算符不为Null,运算结果就是第一个运算符。

一般讲?:放在?.调用的结尾,来做安全调用的收尾工作。

例如

fun strLenSafe(s : String?) : Int = s ?. length ?: 0

帅气一点

fun String?.strLenSafe() : Int = this ?. length ?: 0

不过两种本质上是一样的。

加上 returnthrow 这样的操作是表达式,所以也可以写在 ?: 的右边。

安全类型转换 as?

不见as直接进行类型转换,如果失败就会抛异常。
加上as? 失败就返回null,从而使用?: 来收尾。

例如

override fun equals(o: Any?): Boolean {
       val other = o as? T ?: return false 
       return true;
}

let 函数

当你要给一个接受非空参数的函数传递可空参数时,推荐使用let
let 做的事情其实的把调用它的对象变成了lambda表达式的参数。

从而像这样调用 ?. let { xx(it) }

当然如果要检查多个值是否为null时,还是使用普通的 if 表达式来一次性检查所有的值更简单一些。

非空断言 !!

!! 就是在对编译器咆哮,这个值一定不为空。。
当然如果为空了,就会在你定义 !! 的地方抛出异常。

在一种情况下,比如当你在一个函数中检查了某个值不为null,而在另一个函数中使用这个值,你可以通过逻辑保证这个值一定不为null,但是编译器对这种情况就无能为力了,所以可以使用!!。

延迟初始化 lateinit

很多框架会在你的一个对象实例创建后,用专门的方法来初始化对象。
在这种情况下,如果存在一个可空的属性,那么逻辑上你可以保证,每次使用该属性都是非空的,那么你就可以使用lateinit来标记这个可空属性,避免频繁使用!!或者非空判断。

类型参数可空性

即使不是用?来结尾,参数也有可能是可空的。

泛型,毕竟可空类型也是泛型的一种,如果想避免这个问题,就指定一个非空上界吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值