作者 | 原文 |
---|---|
Nat Pryce | A Whirlwind Tour of the Kotlin Type Hierarchy |
Kotlin 有大量優秀的語言文檔和教程。但是我沒有找到任何描述 Kotlin 類型層級結構(type hierarchy)是如何組成的文章。那真是太可惜了,因為我覺得這個類型系統很棒1。
關于 Kotlin 的類型層級結構只有很少的幾條規則需要了解,這些規則一致和可預測地結合在一起。得益于這些規則,Kotlin 可以提供一些有用的語言特性——空安全性(null safety)、多態性(polymorphism)、不可達代碼分析(unreachable code analysis),而不需要在編譯器和 IDE 中使用一些手段進行特殊處理。
頂層類型
Kotlin 中所有類型被組織成父類型/子類型(supertype/subtype)關系的層級結構。
這個層級結構的頂層是 Any
類型。例如 String
和 Int
都是 Any
的子類型。
Any
相當于 Java 的 Object
類型。與 Java 不同的是 Kotlin 不區分原始類型(primitive type)和其它的類型。它們都是同一類型層級結構的一部分。
如果定義了一個沒有指定父類的類型,則該類型將是 Any
的直接子類型。
class Fruit(val ripeness: Double)
如果你為定義的類型指定了父類,則該父類將是新類型的直接父類型,并且新類型的最終祖先為 Any
。
abstract class Fruit(val ripeness: Double)
class Banana(ripeness: Double, val bendiness: Double):
Fruit(ripeness)
class Peach(ripeness: Double, val fuzziness: Double):
Fruit(ripeness)
如果你的類型實現了多個接口,那么它將具有多個直接的父類型,而 Any
同樣是最終的祖先。
interface ICanGoInASalad
interface ICanBeSunDried
class Tomato(ripeness: Double):
Fruit(ripeness),
ICanGoInASalad,
ICanBeSunDried
Kotlin 的類型檢查器實施父類型/子類型關系。
例如你可以將子類型值存儲到父類型變量中:
var f: Fruit = Banana(bendiness=0.5)
f = Peach(fuzziness=0.8)
但是你不能將父類型值存儲到子類型變量中:
val b = Banana(bendiness=0.5)
val f: Fruit = b
val b2: Banana = f
// Error: Type mismatch: inferred type is Fruit but Banana was expected
可空類型(Nullable Types)
與 Java 不同,Kotlin 區分非空(non-null)和可空(nullable)類型。到目前為止,我們看到的類型都是非空類型,Kotlin 不允許 null
作為這些類型的值。訪問非空類型的變量將永遠不會拋出空指針異常。
類型檢查器拒絕嘗試在非空類型上使用 null
或可空類型的代碼。
例如:
var s : String = null
// Error: Null can not be a value of a non-null type String
如果一個變量存儲的值可能為空,則需要使用與值對應的可空類型。例如 String?
類型是與 String
對應的可空類型,String?
類型的變量可以為任意的 String
值或者 null
。
var s : String? = null
s = "foo"
s = null
s = bar
類型檢查器能確保你在使用一個可空類型的變量前不會忘記檢查是否非空。Kotlin 提供了一些操作符用以便捷的使用可空類型。有關例子請參閱 Kotlin 語言文檔的 Null Safety 部分。
可空類型具有與對應的非空類型相同的層級結構。例如 String
是 Any
的子類型,則 String?
是 Any?
的子類型;Banana
是 Fruit
的子類型,則 Banana?
是 Fruit?
的子類型。
Any
是非空類型層級結構的頂層,Any?
則是可空類型層級結構的頂層。因為 Any?
是 Any
的父類型,所以 Any?
是 Kotlin 類型層級結構的最頂端。
非空類型是其對應可空類型的子類型。例如 String
作為 Any
的子類型,同時也是 String?
的子類型。
這就是為什么可以將非空的 String
值存儲到可空的 String?
變量中,但是不能將可空的 String?
值存儲到非空的 String
變量中。Kotlin 的空安全性不是由特殊規則實施的,而是可空類型與非空類型之間父類型/子類型關系的結果。
這一規則也適用于自定義的類型。
Unit
Kotlin 是一種表達式導向的語言,所有流程控制語句都是表達式。它沒有 Java 和 C 中的 void 函數,函數總是會返回一個值。通常我們為了副作用(side effect)而調用的那些沒有實際計算任何東西的函數,將會返回 Unit
——一種只有一個值的類型。
大多數情況下,你不需要明確指定 Unit
作為返回類型或從函數返回 Unit
。如果編寫的函數具有塊代碼體,并且不指定返回類型,則編譯器會將其視為返回 Unit
類型,否則編譯器會使用推斷的類型。
fun example() {
println("block body and no explicit return type, so returns Unit")
}
val u: Unit = example()
Unit
并沒什么特別之處。就像任何其他類型,它是 Any
的子類型,而 Unit?
是 Any?
的子類型。
Unit?
類型是一個奇怪的特殊例子,這是 Kotlin 的類型系統一致性的結果。Unit?
類型只有兩個值:Unit
單例和 null
。我從來沒有發現需要明確使用 Unit?
類型的地方,但是在類型系統中沒有特殊的 void
這一事實,使得處理各種函數泛型變得更加容易。
Nothing
在 Kotlin 類型層級結構的最底層是 Nothing
類型。
顧名思義,Nothing
是沒有實例的類型。Nothing
類型的表達式不會產生任何值。
注意 Unit
和 Nothing
之間的區別,對 Unit
類型的表達式求值將返回 Unit
的單例,而對 Nothing
類型的表達式求值則永遠都不會返回。
這意味著任何類型為 Nothing
的表達式之后的所有代碼都是無法得到執行的(unreachable code),編譯器和 IDE 會向你發出警告。
什么樣的表達式類型為 Nothing
呢?流程控制中與跳轉相關的表達式。
例如 throw
關鍵字打斷表達式的計算,并從函數中拋出異常。因此 throw
就是 Nothing
類型的表達式。
通過將 Nothing
作為所有類型的子類型,類型系統允許程序中的任何表達求值失敗。這是真實世界的模型,例如 JVM 在計算表達式時內存不足,或者是有人拔掉了計算機的電源插頭。這也意味著我們可以從任何表達式中拋出異常。
fun formatCell(value: Double): String =
if (value.isNaN())
throw IllegalArgumentException("$value is not a number")
else
value.toString()
你可能會驚奇地發現,return
語句的類型也為 Nothing
。return
是一個流程控制語句,它立即從函數中返回一個值,打斷其所在表達式的求值。
fun formatCellRounded(value: Double): String =
val rounded: Long = if (value.isNaN()) return "#ERROR" else Math.round(value)
rounded.toString()
進入無限循環或殺死當前進程的函數返回類型也為 Nothing
。例如 Kotlin 標準庫將 exitProcess
函數聲明為:
fun exitProcess(status: Int): Nothing
如果你編寫自己的返回 Nothing
的函數,編譯器同樣能檢查出調用函數后無法得到執行的代碼,就像使用語言本身的流程控制語句一樣。
inline fun forever(action: ()->Unit): Nothing {
while(true) action()
}
fun example() {
forever {
println("doing...")
}
println("done") // Warning: Unreachable code
}
與空安全一樣,不可達代碼分析是類型系統的一個特性。無需像 Java 一樣在編譯器和 IDE 中使用一些手段進行特殊處理。
可空的 Nothing?
Nothing
像任何其他類型一樣,如果允許其為空則可以得到對應的類型 Nothing?
。Nothing?
只能包含一個值:null
。事實上 Nothing?
就是 null
的類型。
Nothing?
是所有可空類型的最終子類型,所以我們可以使用 null
作為任何可空類型的值。
結論
當你同時考慮這一切時,可能會覺得 Kotlin 的整個類型層級結構相當復雜。
但不要害怕!
我希望這篇文章能證明 Kotlin 有一個簡單而一致的類型系統。只有很少的幾條規則需要了解:這是一個父類型/子類型關系的層級結構,而 Any?
在頂層,Nothing
在底層,以及非空類型是對應可空類型的子類型。就這么多了,沒有其它特殊規則。一些有用語言特性,如空安全性、面向對象多態性、不可達代碼分析都是由這些簡單,可預測的規則引起的。得益于這種一致性,Kotlin 的類型檢查器是一個強有力的工具,可以幫助你編寫簡潔正確的程序。
<a id="neat">1.</a> “很棒(neat)” 的意思是 “優雅巧妙高效”,而不是凱文 · 科斯特納在麥當娜的舞臺上表現出的含義<a id="neat"> </a>