Kotlin知識概括(六) —— 類型系統

前序

      Kotlin引入可空性的新特性,旨在消除來自代碼空引用的危險。將運行時的NPE轉變成編譯器的錯誤。android

可空類型與非空類型

      在Kotlin類型系統中,分爲可空類型和非空類型。當你容許一個變量爲null時,須要顯示在類型後面加上一個問號,將其非空類型轉換爲可空類型。安全

      常見的類型都是非空類型,不能存儲null引用,只有在類型後面添加個問號轉換爲可空類型後,變量纔可存儲null引用。bash

val str:String? = null
複製代碼

      對於一個可空類型的值,不能直接調用該類型的方法,也不能把他賦值給非空類型,更不能把它傳遞給接受非空類型參數的函數。可空類型看似和非空類型並無什麼交互性,但其實並非,只是須要對可空類型進行一個判空後,才能正常交互:函數

val str:String? = 「」
if (str != null)
    str.length
複製代碼

      一旦對可空類型的對象進行判空,編譯器就會對判空的做用域內把該對象看成非空對待。post

Kotlin的可空性與Java的Optional

      Java8中引入的特殊包裝類型Optional來解決null引用問題。但這種方法使代碼更加冗長,而且額外的包裝類還影響運行時的性能,所以並無被普遍使用起來。性能

      但在Kotlin中,可空和非空的對象在運行時沒有什麼區別,可空類型並非非空類型的包裝類。全部的檢查都是編譯器完成,這使得Kotlin的可空類型並不會在運行時帶來額外的開銷。ui

安全調用運算符:?.

      Kotlin標準庫中有一個高效的安全調度運算符:?. 。它將null檢查和調用合併成一個操做。當你使用?.調用一個可空類型對象的方法時,若值不爲空,則方法會被正常執行;若值爲null,則方法調用不發生,並整個表達式返回null。this

安全調用除了能夠調用方法,還能夠用來訪問屬性。spa

Elvis運算符:?:

      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)
複製代碼

      因此只有確保該可空類型對象不爲空時,才使用非空斷言。當使用非空斷言並且發生異常時,異常棧只代表異常發生在哪一行,並不會指明哪一個表達式,因此最好避免同一行中使用非空斷言。

安全轉換:as?

      和常規的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函數

      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"
    }
}
複製代碼

可空性與Java

      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){
    
}
複製代碼

參考資料:

android Kotlin系列:

Kotlin知識概括(一) —— 基礎語法

Kotlin知識概括(二) —— 讓函數更好調用

Kotlin知識概括(三) —— 頂層成員與擴展

Kotlin知識概括(四) —— 接口和類

Kotlin知識概括(五) —— Lambda

Kotlin知識概括(六) —— 類型系統

Kotlin知識概括(七) —— 集合

Kotlin知識概括(八) —— 序列

Kotlin知識概括(九) —— 約定

Kotlin知識概括(十) —— 委託

Kotlin知識概括(十一) —— 高階函數

Kotlin知識概括(十二) —— 泛型

Kotlin知識概括(十三) —— 註解

Kotlin知識概括(十四) —— 反射

相關文章
相關標籤/搜索