瘋狂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
相關文章
相關標籤/搜索