前序
??????Kotlin引入可空性的新特性钙态,旨在消除來(lái)自代碼空引用的危險(xiǎn)。將運(yùn)行時(shí)的NPE轉(zhuǎn)變成編譯器的錯(cuò)誤菇晃。
可空類型與非空類型
??????在Kotlin類型系統(tǒng)中册倒,分為可空類型和非空類型。當(dāng)你允許一個(gè)變量為null時(shí)磺送,需要顯示在類型后面加上一個(gè)問(wèn)號(hào)驻子,將其非空類型轉(zhuǎn)換為可空類型。
??????常見(jiàn)的類型都是非空類型估灿,不能存儲(chǔ)null引用崇呵,只有在類型后面添加個(gè)問(wèn)號(hào)轉(zhuǎn)換為可空類型后,變量才可存儲(chǔ)null引用馅袁。
val str:String? = null
??????對(duì)于一個(gè)可空類型的值域慷,不能直接調(diào)用該類型的方法,也不能把他賦值給非空類型,更不能把它傳遞給接受非空類型參數(shù)的函數(shù)犹褒⌒址模可空類型看似和非空類型并沒(méi)有什么交互性,但其實(shí)并不是化漆,只是需要對(duì)可空類型進(jìn)行一個(gè)判空后,才能正常交互:
val str:String? = “”
if (str != null)
str.length
??????一旦對(duì)可空類型的對(duì)象進(jìn)行判空钦奋,編譯器就會(huì)對(duì)判空的作用域內(nèi)把該對(duì)象當(dāng)作非空對(duì)待座云。
Kotlin的可空性與Java的Optional
??????Java8中引入的特殊包裝類型Optional來(lái)解決null引用問(wèn)題。但這種方法使代碼更加冗長(zhǎng)付材,并且額外的包裝類還影響運(yùn)行時(shí)的性能朦拖,因此并沒(méi)有被廣泛使用起來(lái)。
??????但在Kotlin中厌衔,可空和非空的對(duì)象在運(yùn)行時(shí)沒(méi)有什么區(qū)別璧帝,可空類型并不是非空類型的包裝類。所有的檢查都是編譯器完成富寿,這使得Kotlin的可空類型并不會(huì)在運(yùn)行時(shí)帶來(lái)額外的開(kāi)銷睬隶。
安全調(diào)用運(yùn)算符:?.
??????Kotlin標(biāo)準(zhǔn)庫(kù)中有一個(gè)高效的安全調(diào)度運(yùn)算符:?. 。它將null檢查和調(diào)用合并成一個(gè)操作页徐。當(dāng)你使用?.調(diào)用一個(gè)可空類型對(duì)象的方法時(shí)苏潜,若值不為空,則方法會(huì)被正常執(zhí)行变勇;若值為null恤左,則方法調(diào)用不發(fā)生,并整個(gè)表達(dá)式返回null搀绣。
安全調(diào)用除了可以調(diào)用方法飞袋,還可以用來(lái)訪問(wèn)屬性。
Elvis運(yùn)算符:?:
??????Elvis運(yùn)算符?:用來(lái)提供替代null的默認(rèn)值链患。Elvis運(yùn)算符接收兩個(gè)表達(dá)式巧鸭,如果左側(cè)表達(dá)式非空,則返回其左側(cè)表達(dá)式锣险。當(dāng)左側(cè)表達(dá)式為空蹄皱,則返回右側(cè)表達(dá)式。
Elvis運(yùn)算符經(jīng)常與安全調(diào)度運(yùn)算符一起使用:
val str:String? = null
println(str?.length ?: 0)
Elvis運(yùn)算符也可以配合return 和 throw一起使用芯肤,當(dāng)運(yùn)算符左邊為null時(shí)巷折,能提前返回函數(shù)或拋出異常。
val str:String? = null
//為空拋一次
val length = str?.length ?: throw IllegalArgumentException()
println(length)
str?.let {
println(length)
} ?: return
//等價(jià)于
if(str == null)
//函數(shù)類型為空時(shí)直接打斷函數(shù)繼續(xù)執(zhí)行
return
//str不為null,則繼續(xù)執(zhí)行崖咨。
println(length)
也可以配合run函數(shù)配合使用锻拘,替代if-lese:
str?.let {
//str不為空的邏輯
} ?: run {
//str為空時(shí)邏輯
}
非空斷言:!!
??????Kotlin為NPE愛(ài)好者提供非空斷言運(yùn)算符 !! (雙感嘆號(hào)),可以把任何對(duì)象轉(zhuǎn)換成非空類型,從而調(diào)用該對(duì)象方法署拟,但可能造成拋出NPE婉宰。
val str:String? = null
//拋NPE
println(str!!.length)
??????所以只有確保該可空類型對(duì)象不為空時(shí),才使用非空斷言推穷。當(dāng)使用非空斷言而且發(fā)生異常時(shí)心包,異常棧只表明異常發(fā)生在哪一行,并不會(huì)指明哪個(gè)表達(dá)式馒铃,所以最好避免同一行中使用非空斷言蟹腾。
安全轉(zhuǎn)換:as?
??????和常規(guī)的Java轉(zhuǎn)換一樣,當(dāng)被轉(zhuǎn)換的值不是你視圖轉(zhuǎn)換的類型時(shí)区宇,會(huì)拋出ClassCastException異常娃殖。一般解決方案是在使用在轉(zhuǎn)換前使用is檢查來(lái)確定該值是否符合轉(zhuǎn)換類型。但Kotlin提供更簡(jiǎn)潔的運(yùn)算符——安全轉(zhuǎn)換運(yùn)算符:as?
//定義父類和子類
open class Animal{
fun getName(){
}
}
class Dog:Animal(){
fun getDogName(){
}
}
fun main(args:Array<String>){
val animal:Animal = Dog()
val dog = animal as? Dog ?: return
dog.getDogName()
}
安全轉(zhuǎn)換運(yùn)算符嘗試將值轉(zhuǎn)換成給定的類型议谷,否則返回null:
let函數(shù)
??????let函數(shù)將調(diào)用它的對(duì)象變成lambda表達(dá)式的參數(shù)炉爆。配合安全調(diào)度運(yùn)算符可以把調(diào)用let函數(shù)的可空對(duì)象,轉(zhuǎn)變成非空類型卧晓。然后在let函數(shù)中調(diào)用一系列對(duì)該可空類型的操作芬首。
fun main(args:Array<String>){
val str:String? = null
str?.let {
daqi(it)
}
}
fun daqi(str:String){
}
??????當(dāng)需要檢查多個(gè)值是否為null時(shí),不建議使用嵌套的let調(diào)用來(lái)處理逼裆,建議使用一個(gè)if語(yǔ)句對(duì)這些值進(jìn)行一次性檢查衩辟。
可空類型的擴(kuò)展
??????對(duì)可空類型的進(jìn)行擴(kuò)展的好處是,允許接收者為null時(shí)調(diào)用擴(kuò)展函數(shù)波附,并在擴(kuò)展函數(shù)中處理null,而不用確保變量不為null后再調(diào)用該對(duì)象的方法艺晴。因?yàn)楫?dāng)實(shí)例為null時(shí),成員方法永遠(yuǎn)不會(huì)被執(zhí)行掸屡。
??????Kotlin標(biāo)準(zhǔn)庫(kù)中的CharSequence存在兩個(gè)擴(kuò)展函數(shù):isNullOrEmpty和isNullOrBlank封寞,可以由String?類型的接收者調(diào)用。
??????對(duì)可空類型定義擴(kuò)展函數(shù)時(shí)仅财,意味著函數(shù)體中的this可能為空狈究,需要做對(duì)應(yīng)的空處理。
fun String?.daqi(){
if (this == null){
println("this is null")
}
}
fun main(args:Array<String>){
val str:String? = null
由于接收的是可空類型盏求,不需要使用?.
str.daqi()
}
延遲初始化
??????Kotlin中抖锥,屬性聲明為非空類型時(shí),必須在構(gòu)造函數(shù)中初始化碎罚。但屬性可以在一個(gè)特殊的方法中磅废,通過(guò)依賴注入來(lái)初始化。這時(shí)不能在構(gòu)造函數(shù)中為屬性提供一個(gè)非空初始化器荆烈,但你仍想將該類型聲明為非空類型拯勉,避免空檢查竟趾。可以使用lateinit關(guān)鍵字修飾該變量宫峦,請(qǐng)將該變量使用var修飾岔帽,因?yàn)関al必須會(huì)編譯成必須在構(gòu)造方法中初始化的final字段。
class daqi{
private lateinit var name:String
fun onCreate(){
name = "daqi"
}
}
可空性與Java
??????Kotlin會(huì)根據(jù)Java中的可空性注解导绷,來(lái)對(duì)來(lái)自Java的類型分為可空類型和非空類型犀勒。如,@Nullable注解的對(duì)象妥曲,會(huì)被Kotlin當(dāng)作可空類型的對(duì)象账蓉。@Notnull注解的對(duì)象,會(huì)被Kotlin當(dāng)作非空類型的對(duì)象逾一。
??????當(dāng)可空性注解不存在時(shí),Java類型會(huì)被轉(zhuǎn)換為Kotlin的平臺(tái)類型肮雨。平臺(tái)類型本質(zhì)上是Kotlin不知道其可空信息遵堵,既可以把它當(dāng)作可空類型,也可以把它當(dāng)作非空類型怨规。如果選擇非空類型陌宿,編譯器會(huì)在賦值時(shí)觸發(fā)一個(gè)斷言,防止Kotlin的非空變量保存空值波丰。這意味著需要開(kāi)發(fā)者負(fù)責(zé)正確處理來(lái)自Java的值壳坪。
??????Kotlin定義的函數(shù)中,編譯器會(huì)生成對(duì)每個(gè)非空類型的參數(shù)的檢查掰烟,如果使用不正確的參數(shù)調(diào)用爽蝴,會(huì)立即拋出異常。(這種檢查在函數(shù)調(diào)用的時(shí)候就被執(zhí)行了纫骑,而不是等到該異常參數(shù)被使用時(shí)才執(zhí)行蝎亚。)
基本數(shù)據(jù)類型
??????Java區(qū)分基本數(shù)據(jù)類型和引用類型,基本數(shù)據(jù)類型具有高效存儲(chǔ)和傳遞的性質(zhì)先馆。當(dāng)你需要在泛型類中存儲(chǔ)一些基本數(shù)據(jù)類型時(shí)发框,需要以基本數(shù)據(jù)類型的包裝類型進(jìn)行存儲(chǔ)。因?yàn)镴VM不支持用基本數(shù)據(jù)類型作為類型參數(shù)煤墙。
??????Kotlin并不區(qū)分基本類型和包裝類型梅惯。對(duì)于變量、屬性和返回類型仿野,Kotlin的基本數(shù)據(jù)類型會(huì)被編譯成Java的基礎(chǔ)數(shù)據(jù)類型铣减。只有對(duì)于泛型類時(shí),才會(huì)被編譯器成對(duì)應(yīng)的Java基本類型包裝類脚作。
基本數(shù)據(jù)類型
??????當(dāng)使用Java聲明的基本數(shù)據(jù)類型變量時(shí)徙歼,該類型會(huì)變成非空類型,而不是平臺(tái)類型。因?yàn)镴ava的基本數(shù)據(jù)類型不能存儲(chǔ)null值魄梯。
??????Kotlin中可空的基本數(shù)據(jù)類型會(huì)被編譯成對(duì)應(yīng)的包裝類型桨螺,因?yàn)镴ava的基本數(shù)據(jù)類型不能存儲(chǔ)null值。
數(shù)字轉(zhuǎn)換
??????kotlin不會(huì)自動(dòng)把數(shù)字從一種類型轉(zhuǎn)換成另一種取值范圍更大的類型酿秸。Kotlin為每種基本數(shù)據(jù)類型(Boolean除外)都定義了轉(zhuǎn)換到其他基本數(shù)據(jù)類型的函數(shù)灭翔。
??????Kotlin要求轉(zhuǎn)換必須顯式的,因?yàn)樵贘ava中辣苏,比較裝箱值時(shí)肝箱,不僅檢查他們存儲(chǔ)的值,還會(huì)比較裝箱類型稀蟋。
//此處比較會(huì)返回false
new Integer(42).equals(new Long(42))
??????Kotlin標(biāo)準(zhǔn)庫(kù)為字符串也提供了轉(zhuǎn)換為基本數(shù)據(jù)類型的擴(kuò)展函數(shù)煌张。如果對(duì)字符串解析失敗,則拋出NumberFormatException()方法退客。
根類型
??????Any類型是所有Kotlin非空類型的超類骏融。但Any不能持有null值,當(dāng)需要持有任何值的變量包括null值萌狂,必須使用Any?
??????Any只包含toString档玻、equals和hashCode。所有Kotlin的這些方法都是從Any中繼承來(lái)得茫藏。但Any不能使用使用其他Object的方法(如:wait和notify)
類型參數(shù)的可空性
??????Kotlin中所以泛型類和泛型函數(shù)的類型參數(shù)默認(rèn)都是可空的误趴,因?yàn)槟J(rèn)上界是Any?
??????如果需要類型參數(shù)非空,則必須為其指定一個(gè)非空的上界:
fun <T:Any> daqi(t:T){
}
參考資料:
- 《Kotlin實(shí)戰(zhàn)》
- Kotlin官網(wǎng)
android Kotlin系列:
Kotlin知識(shí)歸納(一) —— 基礎(chǔ)語(yǔ)法
Kotlin知識(shí)歸納(二) —— 讓函數(shù)更好調(diào)用
Kotlin知識(shí)歸納(三) —— 頂層成員與擴(kuò)展
Kotlin知識(shí)歸納(六) —— 類型系統(tǒng)