Kotlin引入可空性的新特性,旨在消除來自代碼空引用的危險。將運行時的NPE轉變成編譯器的錯誤。android
在Kotlin類型系統中,分爲可空類型和非空類型。當你容許一個變量爲null時,須要顯示在類型後面加上一個問號,將其非空類型轉換爲可空類型。安全
常見的類型都是非空類型,不能存儲null引用,只有在類型後面添加個問號轉換爲可空類型後,變量纔可存儲null引用。bash
val str:String? = null
複製代碼
對於一個可空類型的值,不能直接調用該類型的方法,也不能把他賦值給非空類型,更不能把它傳遞給接受非空類型參數的函數。可空類型看似和非空類型並無什麼交互性,但其實並非,只是須要對可空類型進行一個判空後,才能正常交互:函數
val str:String? = 「」
if (str != null)
str.length
複製代碼
一旦對可空類型的對象進行判空,編譯器就會對判空的做用域內把該對象看成非空對待。post
Java8中引入的特殊包裝類型Optional來解決null引用問題。但這種方法使代碼更加冗長,而且額外的包裝類還影響運行時的性能,所以並無被普遍使用起來。性能
但在Kotlin中,可空和非空的對象在運行時沒有什麼區別,可空類型並非非空類型的包裝類。全部的檢查都是編譯器完成,這使得Kotlin的可空類型並不會在運行時帶來額外的開銷。ui
Kotlin標準庫中有一個高效的安全調度運算符:?. 。它將null檢查和調用合併成一個操做。當你使用?.調用一個可空類型對象的方法時,若值不爲空,則方法會被正常執行;若值爲null,則方法調用不發生,並整個表達式返回null。this
安全調用除了能夠調用方法,還能夠用來訪問屬性。spa
Elvis運算符?:用來提供替代null的默認值。Elvis運算符接收兩個表達式,若是左側表達式非空,則返回其左側表達式。當左側表達式爲空,則返回右側表達式。.net
Elvis運算符常常與安全調度運算符一塊兒使用:
val str:String? = null
println(str?.length ?: 0)
複製代碼
Elvis運算符也能夠配合return 和 throw一塊兒使用,當運算符左邊爲null時,能提早返回函數或拋出異常。
val str:String? = null
//爲空拋一次
val length = str?.length ?: throw IllegalArgumentException()
println(length)
複製代碼
str?.let {
println(length)
} ?: return
//等價於
if(str == null)
//函數類型爲空時直接打斷函數繼續執行
return
//str不爲null,則繼續執行。
println(length)
複製代碼
也能夠配合run函數配合使用,替代if-lese:
str?.let {
//str不爲空的邏輯
} ?: run {
//str爲空時邏輯
}
複製代碼
Kotlin爲NPE愛好者提供非空斷言運算符 !! (雙感嘆號),能夠把任何對象轉換成非空類型,從而調用該對象方法,但可能形成拋出NPE。
val str:String? = null
//拋NPE
println(str!!.length)
複製代碼
因此只有確保該可空類型對象不爲空時,才使用非空斷言。當使用非空斷言並且發生異常時,異常棧只代表異常發生在哪一行,並不會指明哪一個表達式,因此最好避免同一行中使用非空斷言。
和常規的Java轉換同樣,當被轉換的值不是你視圖轉換的類型時,會拋出ClassCastException異常。通常解決方案是在使用在轉換前使用is檢查來肯定該值是否符合轉換類型。但Kotlin提供更簡潔的運算符——安全轉換運算符: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()
}
複製代碼
安全轉換運算符嘗試將值轉換成給定的類型,不然返回null:
let函數將調用它的對象變成lambda表達式的參數。配合安全調度運算符能夠把調用let函數的可空對象,轉變成非空類型。而後在let函數中調用一系列對該可空類型的操做。
fun main(args:Array<String>){
val str:String? = null
str?.let {
daqi(it)
}
}
fun daqi(str:String){
}
複製代碼
當須要檢查多個值是否爲null時,不建議使用嵌套的let調用來處理,建議使用一個if語句對這些值進行一次性檢查。
對可空類型的進行擴展的好處是,容許接收者爲null時調用擴展函數,並在擴展函數中處理null,而不用確保變量不爲null後再調用該對象的方法。由於當實例爲null時,成員方法永遠不會被執行。
Kotlin標準庫中的CharSequence存在兩個擴展函數:isNullOrEmpty和isNullOrBlank,能夠由String?類型的接收者調用。
對可空類型定義擴展函數時,意味着函數體中的this可能爲空,須要作對應的空處理。
fun String?.daqi(){
if (this == null){
println("this is null")
}
}
fun main(args:Array<String>){
val str:String? = null
因爲接收的是可空類型,不須要使用?.
str.daqi()
}
複製代碼
Kotlin中,屬性聲明爲非空類型時,必須在構造函數中初始化。但屬性能夠在一個特殊的方法中,經過依賴注入來初始化。這時不能在構造函數中爲屬性提供一個非空初始化器,但你仍想將該類型聲明爲非空類型,避免空檢查。可使用lateinit關鍵字修飾該變量,請將該變量使用var修飾,由於val必須會編譯成必須在構造方法中初始化的final字段。
class daqi{
private lateinit var name:String
fun onCreate(){
name = "daqi"
}
}
複製代碼
Kotlin會根據Java中的可空性註解,來對來自Java的類型分爲可空類型和非空類型。如,@Nullable註解的對象,會被Kotlin看成可空類型的對象。@Notnull註解的對象,會被Kotlin看成非空類型的對象。
當可空性註解不存在時,Java類型會被轉換爲Kotlin的平臺類型。平臺類型本質上是Kotlin不知道其可空信息,既能夠把它看成可空類型,也能夠把它看成非空類型。若是選擇非空類型,編譯器會在賦值時觸發一個斷言,防止Kotlin的非空變量保存空值。這意味着須要開發者負責正確處理來自Java的值。
Kotlin定義的函數中,編譯器會生成對每一個非空類型的參數的檢查,若是使用不正確的參數調用,會當即拋出異常。(這種檢查在函數調用的時候就被執行了,而不是等到該異常參數被使用時才執行。)
Java區分基本數據類型和引用類型,基本數據類型具備高效存儲和傳遞的性質。當你須要在泛型類中存儲一些基本數據類型時,須要以基本數據類型的包裝類型進行存儲。由於JVM不支持用基本數據類型做爲類型參數。
Kotlin並不區分基本類型和包裝類型。對於變量、屬性和返回類型,Kotlin的基本數據類型會被編譯成Java的基礎數據類型。只有對於泛型類時,纔會被編譯器成對應的Java基本類型包裝類。
當使用Java聲明的基本數據類型變量時,該類型會變成非空類型,而不是平臺類型。由於Java的基本數據類型不能存儲null值。
Kotlin中可空的基本數據類型會被編譯成對應的包裝類型,由於Java的基本數據類型不能存儲null值。
kotlin不會自動把數字從一種類型轉換成另外一種取值範圍更大的類型。Kotlin爲每種基本數據類型(Boolean除外)都定義了轉換到其餘基本數據類型的函數。
Kotlin要求轉換必須顯式的,由於在Java中,比較裝箱值時,不只檢查他們存儲的值,還會比較裝箱類型。
//此處比較會返回false
new Integer(42).equals(new Long(42))
複製代碼
Kotlin標準庫爲字符串也提供了轉換基本數據類型的擴展函數。若是對字符串解析失敗,則拋出NumberFormatException()方法。
Any類型是全部Kotlin非空類型的超類。但Any不能持有null值,當須要持有任何值的變量包括null值,必須使用Any?
Any只包含toString、equals和hashCode。全部Kotlin的這些方法都是從Any中繼承來得。但Any不能使用使用其餘Object的方法(如:wait和notify)
Kotlin中因此泛型類和泛型函數的類型參數默認都是可空的,由於默認上界是Any?
若是須要類型參數非空,則必須爲其指定一個非空的上界:
fun <T:Any> daqi(t:T){
}
複製代碼