空型別
Kotlin 跟 Java 的最大不同應當就屬空型別這點了,使用 Kotlin 開發,IDE 會智慧的對可能為空的地方進行報錯提示,開發者必須處理該錯誤,否則連編譯都通過不了,從而降低程式 NullPointException 異常的出現機率,所以,一般情況下使用 Kotlin 開發很少見到 NPE 異常。
非空與可空型別fun getName(): String { return "lqr"}
這是一個很普通的函式宣告,它指明瞭函式返回值是一個 String 型別,對此,Kotlin 會認為這是一個不可能返回 null 結果的函式,那如果我就是要返回 null 會怎樣?
// IDE報錯:Null can not be a value of a non-null type Stringfun getName(): String { return null}
該 IDE 的報錯提示說明了函式返回值型別 String 是一個不能為 null 的值,即非空型別。如果需要函式可以返回 null 的話,需要對函式返回值型別做一點小修改,使其可以用空,這僅僅只需要在返回值型別後面追加 ? 即可:
fun getName(): String? { return null}
綜上,Kotlin 的型別宣告分為兩類(包括但不限於函式返回值型別),分別是:
非空型別:單純宣告的類就是非空型別,如:String。可空型別:透過在類後面放置 ? 來宣告,如:String?。可空型別運算子Kotlin 為保證程式碼空安全,提供了幾種處理方式,本節主要陳述其中的 3 種運算子。
安全呼叫運算子(?.)以獲取字串長度為例,非空型別變數直接透過 .length 即可:
val name: String = "lqr"println(name.length)
而可空型別變數,不僅需要在型別宣告時使用 ? ,在呼叫可空型別物件的成員變數 length 時也需要使用 ?. 進行處理:
val name: String? = nullprintln(name?.length) // 輸出:null
因為 name 的值為 null,所以 .length 並不會被執行,因此這程式碼相當於 println(null),雖然結果是 null,但程式並不會崩潰。
安全呼叫運算子(?.):如果接收者非空,就呼叫一個方法或訪問一個屬性,否則不執行。
Elvis 運算子(?:)日常開發中,我們經常習慣於用一行程式碼同時處理變數非空或為空的情況,在 Kotlin 中,藉助 if-else 程式碼可以這麼寫:
println(if (getName() != null) getName() else "Default Name")
Java 有三元運算子,而 Kotlin 沒有,所以這裡只能用 if-else。
Kotlin 還提供了 Elvis 運算子(?:),可以對 if-else 進行簡化:
println(getName() ?: "Default Name")
Elvis 運算子(?:):如果 ?: 左側表示式非空,elvis 運算子就返回其左側表示式,否則返回右側表示式。請注意,當且僅當左側為空時,才會對右側表示式求值。
非空斷言運算子(!!)如果你非常非常確定變數的值絕對不可能為 null,那麼你可以在物件呼叫時使用 !! 對其進行轉換成非空型別:
val value: String? = "Hello LQR"println(value!!.length)
非空斷言運算子(!!):將任何值轉換為非空型別,若該值為空則丟擲異常。
智慧型別轉換型別轉換在開發中很常見,特別是在多型的應用情景裡,會使用父類變數接收子類物件,並且可能會需要強轉成具體的子類型別以使用特定的子類功能。
open class Parent {}class Child : Parent() { fun getName(): String { return "lqr" }}fun main(args: Array<String>) { val man: Parent = Child() println((man as Child).getName())}
Kotlin 裡,強轉需要使用 as 關鍵字來處理。
當然了,這裡的程式碼處理的很不好,通常在轉換前會先判斷物件的具體型別後再做強轉,以免出現型別轉換異常,因此,程式碼可以修改為:
val man: Parent = Child()if (man is Child) { println(man.getName())}
Kotlin 中使用 is 關鍵字來判斷變數型別,if 程式碼塊中,man 變數已經被識別為 Child 型別了,因此不再需要顯式強轉,這就是 Kotlin 的智慧型別轉換,反觀 Java 就顯的有些笨笨的了:
Parent man = new Child();if (man instanceof Child) { System.out.println(((Child) man).getName());}
回過頭再來看看 as 關鍵字,Kotlin 程式碼中使用 as 進行物件的型別強轉,如果我們不先進行型別判斷,就直接強制變數型別,一旦被強轉的物件型別有誤,就必定會丟擲ClassCastException:
// Exception in thread "main" java.lang.ClassCastException: com.charylin.kotlinlearn.Parent cannot be cast to com.charylin.kotlinlearn.Childval parent: Parent = Parent()val child: Child = parent as Childprint(child)
還好,Kotlin 的智慧型別轉換功能為 "直接強轉黨" 提供了一條出路,那就是使用 as?,同時如果變數有顯式指定型別的話,需要將其改為可空型別,或者乾脆把變數型別宣告去掉:
val parent: Parent = Parent()val child: Child? = parent as? Child// val child = parent as? Child // 這種寫法也是OK的print(child) // 輸出:null
as? 相比 as 要智慧一些,當強制型別有誤會時,結果會為 null。