疯狂kotlin讲义连载之Kotlin的基础类型--null安全

null安全能够说是Kotlin语言对Java的重大改进之一,这样能够避免Java编程时使人恐惧的“NullPointerException”(简称:NPE)。但话说回来,null安全不过是各类现代语言玩剩下的东西。
1 非空类型和可空类型
下面先看一个简单的例子。
程序清单:codes\02\2.8\NullableTest.kt
html

fun main(args: Array<String>) {
var str = "fkit"
// 因为str转换为Int有可能失败,故num有可能没有值
// 所以不能使用Int来声明num的类型
var num: Int = str.toIntOrNull()
var num: Int? = str.toIntOrNull()
println(num)
}


对比上面两行粗体字代码:第一行代码声明num的类型为Int,第二行代码声明num的类型为Int?。程序中第一行粗体字代码没法经过编译,第二行粗体字代码能经过编译。其中Int?就是可空类型,这种类型的变量可接受Int值和null两种状况;而Int类型的变量则只接受Int值,不能接受null的状况。因为str是一个String变量,当程序试图把String变量转换为Int值时,有可能转换成功(若是变量值是形如"123"的字符串),也有可能转换失败(本程序就转换失败了)。转换失败时,就没法成功返回Int值,此时将会返回null,所以必须使用Int?类型的变量来存储转换结果。
对于可能发生“值缺失”的状况,编译器会自动推断该变量的类型为可空类型。例如以下代码:
java

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


须要指出的是,只有可空类型的变量或常量才能接受null,非空类型的变量和常量不能接受null。
Kotlin对可空类型进行了限制:若是不加任何处理,可空类型不容许直接调用方法、访问属性。所以,经过可空类型与非空类型的区分,Kotlin便可在程序中避免空指针异常。例如以下程序(程序清单同上):
编程

var aStr: String = "fkit"
var bStr: String? = "fkit"
aStr = null // 错误,aStr不接受null值
bStr = null // 正确
// 编译经过,aStr不可能为null,运行时不可能致使NPE
println(aStr.length)
// 编译不能经过,不可能致使NPE
println(bStr.length)


上面代码中定义了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数组

un main(args: Array<String>) {
var b: String? = "fkit"
// 先判断b不为null,而后访问b的length属性
var len = if (b != null) b.length else -1
println("b的长度:${len}")
b = null
// 先判断b不为null,而后调用b的length属性
if (b != null && b.length > 0) {
// 访问b的length属性
println(b.length)
}
else {
println("空字符串")
}
}


上面程序定义了String?(可空类型)的变量b,这样程序不能直接调用变量b的方法和属性,Kotlin要求程序先判断b不为null,接下来程序便可在该条件下调用b的方法或属性。
对于非空Boolean类型而言,它能够接受3个值,true、false或null,所以对Boolean?类型变量进行判断时,须要使用Boolean?变量显式与true、false值进行比较。例如以下代码。
f安全

un main(args: Array<String>) {
var b: Boolean? = null
if( b == true ){
println("为真")
}
}
若是将if分支改成以下形式:
if( b ){
println("为真")
}


编译器会报错: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
函数

fun main(args: Array<String>) {
var b: String? = "fkit"
println(b?.length) // 输出4
b = null
println(b?.length) // 输出null
}


上面程序中变量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

// 定义一个元素可空的数组
val myArr: Array<String?> = arrayOf("fkit", "fkjava", null, "crazyit")
for (item in myArr) {
// 当item不为null时才调用let函数
item?.let { println(it) }
}


上面粗体字代码使用安全调用来调用let函数,这样只有当item元素不为null才会执行let函数。上面程序调用let函数时传入一个Lambda表达式做为函数参数。
4 Elvis运算
Elvis运算也是一种早就满大街的小技巧了,其实就是if else的简化写法。对好比下代码。
程序清单:codes\02\2.8\Elvis.kt
指针

fun main(args: Array<String>) {
var b: String? = "fkit"
// 先判断b不为null,而后访问b的length属性
var len1 = if (b != null) b.length else -1
println(len1);
b = null
// 使用Elvis运算符
var len2 = b?.length ?: -1
println(len2);
}


上面第一行粗体字代码使用传统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

fun main(args: Array<String>) {
var b: String? = "fkit"
println(b!!.length) // 输出4
b = null
println(b!!.length) // 删除null
// 定义一个元素可空的数组
val myArr: Array<String?> = arrayOf("fkit", "fkjava", null, "crazyit")
for (item in myArr) {
// 当item不为null时才调用let函数
item!!.let { println(it) }
}
}


上面程序就是将前面的安全调用程序中?.安全调用该为!!.强制调用,当可空变量b为null时,b!!.length将会引起NPE;与前面安全调用相似的是,强制调用也可做用于let()函数,此时无论item元素是否为null,程序都会对该元素调用let()函数,所以也可能致使NPE。cdn

以上内容节选自《疯狂Kotlin讲义》:一本让您最直接认识Kotlin的疯狂讲义
本书即将于2017年11月发售 敬请期待
往期连载
第一期: 第一期:juejin.im/post/59c0b7…

第二期:juejin.im/post/59c1d6…

第三期:juejin.im/post/59e407…

第四期:juejin.im/post/59ed77…

第五期:juejin.im/post/59eec3…

第六期:juejin.im/post/59effb…

第七期:juejin.im/post/59f153…

第八期:juejin.im/post/59f283…

第九期:juejin.im/post/59f686…

第十期:juejin.im/post/59f7ea…

第十一期:juejin.im/post/59f953…

相关书籍《疯狂Android讲义》https://item.jd.com/11689014.html
相关文章
相关标签/搜索