null安全能够说是Kotlin语言对Java的重大改进之一,这样能够避免Java编程时使人恐惧的“NullPointerException”(简称:NPE)。但话说回来,null安全不过是各类现代语言玩剩下的东西。
1 非空类型和可空类型
下面先看一个简单的例子。
程序清单:codes\02\2.8\NullableTest.kt
html
对比上面两行粗体字代码:第一行代码声明num的类型为Int,第二行代码声明num的类型为Int?。程序中第一行粗体字代码没法经过编译,第二行粗体字代码能经过编译。其中Int?就是可空类型,这种类型的变量可接受Int值和null两种状况;而Int类型的变量则只接受Int值,不能接受null的状况。因为str是一个String变量,当程序试图把String变量转换为Int值时,有可能转换成功(若是变量值是形如"123"的字符串),也有可能转换失败(本程序就转换失败了)。转换失败时,就没法成功返回Int值,此时将会返回null,所以必须使用Int?类型的变量来存储转换结果。
对于可能发生“值缺失”的状况,编译器会自动推断该变量的类型为可空类型。例如以下代码:
java
须要指出的是,只有可空类型的变量或常量才能接受null,非空类型的变量和常量不能接受null。
Kotlin对可空类型进行了限制:若是不加任何处理,可空类型不容许直接调用方法、访问属性。所以,经过可空类型与非空类型的区分,Kotlin便可在程序中避免空指针异常。例如以下程序(程序清单同上):
编程
上面代码中定义了aStr和bStr两个变量,其中aStr是非空String类型,所以aStr变量不容许赋值为null;而bStr是可空的String类型,bStr变量容许被赋值为null;程序能够直接调用aStr的方法和属性,但aStr变量的值不可能为nul的,所以能够避免NPE;Kotlin对可空类型进行了限制,可空类型不容许直接调用方法和属性,所以程序不能直接调用bStr的方法或属性,这样也可避免NPE。
2 先判断后使用
可空类型的变量不容许直接调用方法或属性,但能够先判断该变量不为null,而后再去调用该变量的属性和方法。例如以下代码。
程序清单:codes\02\2.8\CheckNull.kt
f数组
上面程序定义了String?(可空类型)的变量b,这样程序不能直接调用变量b的方法和属性,Kotlin要求程序先判断b不为null,接下来程序便可在该条件下调用b的方法或属性。
对于非空Boolean类型而言,它能够接受3个值,true、false或null,所以对Boolean?类型变量进行判断时,须要使用Boolean?变量显式与true、false值进行比较。例如以下代码。
f安全
编译器会报错:type mismatch: inferred type is Boolean? but Boolean was expected,这是由于Kotlin的if条件必须是Boolean类型,而Boolean?与Boolean本质上是两个不一样类型,所以编译器会报错。
3 安全调用
安全调用则在Java中已出现不少年了,只不过没有出如今Java官方语法中——若是读者熟悉Spring EL,必定见过以下用法。
user?.dog?.name
上面表达式语言表示若是user不为null,则返回user的dog属性;若是dog属性值不为null,则继续获取dog属性值的name属性值。反过来,若是user为null,或user.dog为null,上面表达式语言都不会致使NPE,而是整个表达式返回null——这就是Spring EL的安全调用。
Kotlin的安全调用也彻底与此相似,例如以下代码。
程序清单:codes\02\2.8\SafeCall.kt
函数
上面程序中变量b的类型是String?,所以程序使用了?.安全调用来访问b的length属性,当b为null时,程序也不会引起NPE,而是返回null。
与Spring EL相似的是,Kotlin的安全调用彻底也支持链式调用,就像Spring EL的用法同样:
user?.dog?.name
上面代码表示安全获取user的dog的name属性值,若是user或user.dog为null,整个表达式将会返回null。
此外,安全调用还可与let全局函数结合使用,例如以下代码(程序清单同上)。
post
上面粗体字代码使用安全调用来调用let函数,这样只有当item元素不为null才会执行let函数。上面程序调用let函数时传入一个Lambda表达式做为函数参数。
4 Elvis运算
Elvis运算也是一种早就满大街的小技巧了,其实就是if else的简化写法。对好比下代码。
程序清单:codes\02\2.8\Elvis.kt
指针
上面第一行粗体字代码使用传统if分支进行判断,当b不为null时返回b.length,不然返回-1;第二行粗体字代码则使用?:运算符,该运算符就是Elvis——它的含义是:若是?:左边的表达式不为null时,则返回左边表达式的值,不然返回?:右边表达式的值。
因而可知?:其实就是if分支的简化写法。
此外,因为Kotlin的return、throw都属于表达式,所以它们也均可以用在?:运算符的右边。例如以下代码片断:
val data = ……
val email = data["email"] ?: throw IllegalArgumentException("没有指定Email信息!")
5 强制调用
强制调用时为NPE爱好者准备的——若是读者依然喜欢Java那种简单、粗暴的方式:无论变量是否为null,程序都直接调用该变量的方法或属性,Kotlin也为这种用法提供了支持,用!!.便可强制调用可空变量的方法或属性,这样强制调用可能引起NPE。例如以下代码。
程序清单:codes\02\2.8\ForceCall.kt
code
上面程序就是将前面的安全调用程序中?.安全调用该为!!.强制调用,当可空变量b为null时,b!!.length将会引起NPE;与前面安全调用相似的是,强制调用也可做用于let()函数,此时无论item元素是否为null,程序都会对该元素调用let()函数,所以也可能致使NPE。cdn