作者 | 原文 |
---|---|
Nat Pryce | A Whirlwind Tour of the Kotlin Type Hierarchy |
Kotlin 有大量優(yōu)秀的語言文檔和教程役耕。但是我沒有找到任何描述 Kotlin 類型層級結構(type hierarchy)是如何組成的文章。那真是太可惜了萝映,因為我覺得這個類型系統(tǒng)很棒1交惯。
關于 Kotlin 的類型層級結構只有很少的幾條規(guī)則需要了解,這些規(guī)則一致和可預測地結合在一起望迎。得益于這些規(guī)則,Kotlin 可以提供一些有用的語言特性——空安全性(null safety)凌外、多態(tài)性(polymorphism)辩尊、不可達代碼分析(unreachable code analysis),而不需要在編譯器和 IDE 中使用一些手段進行特殊處理康辑。
頂層類型
Kotlin 中所有類型被組織成父類型/子類型(supertype/subtype)關系的層級結構摄欲。
這個層級結構的頂層是 Any
類型。例如 String
和 Int
都是 Any
的子類型疮薇。
Any
相當于 Java 的 Object
類型胸墙。與 Java 不同的是 Kotlin 不區(qū)分原始類型(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)
如果你的類型實現(xiàn)了多個接口呀伙,那么它將具有多個直接的父類型补履,而 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 區(qū)分非空(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 的空安全性不是由特殊規(guī)則實施的嚣鄙,而是可空類型與非空類型之間父類型/子類型關系的結果。
這一規(guī)則也適用于自定義的類型串结。
Unit
Kotlin 是一種表達式導向的語言哑子,所有流程控制語句都是表達式。它沒有 Java 和 C 中的 void 函數(shù)肌割,函數(shù)總是會返回一個值卧蜓。通常我們?yōu)榱烁弊饔茫╯ide effect)而調用的那些沒有實際計算任何東西的函數(shù),將會返回 Unit
——一種只有一個值的類型把敞。
大多數(shù)情況下弥奸,你不需要明確指定 Unit
作為返回類型或從函數(shù)返回 Unit
。如果編寫的函數(shù)具有塊代碼體奋早,并且不指定返回類型盛霎,則編譯器會將其視為返回 Unit
類型,否則編譯器會使用推斷的類型耽装。
fun example() {
println("block body and no explicit return type, so returns Unit")
}
val u: Unit = example()
Unit
并沒什么特別之處愤炸。就像任何其他類型,它是 Any
的子類型掉奄,而 Unit?
是 Any?
的子類型规个。
Unit?
類型是一個奇怪的特殊例子,這是 Kotlin 的類型系統(tǒng)一致性的結果姓建。Unit?
類型只有兩個值:Unit
單例和 null
诞仓。我從來沒有發(fā)現(xiàn)需要明確使用 Unit?
類型的地方,但是在類型系統(tǒng)中沒有特殊的 void
這一事實速兔,使得處理各種函數(shù)泛型變得更加容易狂芋。
Nothing
在 Kotlin 類型層級結構的最底層是 Nothing
類型。
顧名思義憨栽,Nothing
是沒有實例的類型。Nothing
類型的表達式不會產生任何值。
注意 Unit
和 Nothing
之間的區(qū)別屑柔,對 Unit
類型的表達式求值將返回 Unit
的單例屡萤,而對 Nothing
類型的表達式求值則永遠都不會返回。
這意味著任何類型為 Nothing
的表達式之后的所有代碼都是無法得到執(zhí)行的(unreachable code)掸宛,編譯器和 IDE 會向你發(fā)出警告死陆。
什么樣的表達式類型為 Nothing
呢?流程控制中與跳轉相關的表達式唧瘾。
例如 throw
關鍵字打斷表達式的計算措译,并從函數(shù)中拋出異常。因此 throw
就是 Nothing
類型的表達式饰序。
通過將 Nothing
作為所有類型的子類型领虹,類型系統(tǒng)允許程序中的任何表達求值失敗。這是真實世界的模型求豫,例如 JVM 在計算表達式時內存不足塌衰,或者是有人拔掉了計算機的電源插頭。這也意味著我們可以從任何表達式中拋出異常蝠嘉。
fun formatCell(value: Double): String =
if (value.isNaN())
throw IllegalArgumentException("$value is not a number")
else
value.toString()
你可能會驚奇地發(fā)現(xiàn)最疆,return
語句的類型也為 Nothing
。return
是一個流程控制語句蚤告,它立即從函數(shù)中返回一個值努酸,打斷其所在表達式的求值。
fun formatCellRounded(value: Double): String =
val rounded: Long = if (value.isNaN()) return "#ERROR" else Math.round(value)
rounded.toString()
進入無限循環(huán)或殺死當前進程的函數(shù)返回類型也為 Nothing
杜恰。例如 Kotlin 標準庫將 exitProcess
函數(shù)聲明為:
fun exitProcess(status: Int): Nothing
如果你編寫自己的返回 Nothing
的函數(shù)获诈,編譯器同樣能檢查出調用函數(shù)后無法得到執(zhí)行的代碼,就像使用語言本身的流程控制語句一樣箫章。
inline fun forever(action: ()->Unit): Nothing {
while(true) action()
}
fun example() {
forever {
println("doing...")
}
println("done") // Warning: Unreachable code
}
與空安全一樣烙荷,不可達代碼分析是類型系統(tǒng)的一個特性。無需像 Java 一樣在編譯器和 IDE 中使用一些手段進行特殊處理檬寂。
可空的 Nothing?
Nothing
像任何其他類型一樣终抽,如果允許其為空則可以得到對應的類型 Nothing?
。Nothing?
只能包含一個值:null
桶至。事實上 Nothing?
就是 null
的類型昼伴。
Nothing?
是所有可空類型的最終子類型,所以我們可以使用 null
作為任何可空類型的值镣屹。
結論
當你同時考慮這一切時圃郊,可能會覺得 Kotlin 的整個類型層級結構相當復雜。
但不要害怕女蜈!
我希望這篇文章能證明 Kotlin 有一個簡單而一致的類型系統(tǒng)持舆。只有很少的幾條規(guī)則需要了解:這是一個父類型/子類型關系的層級結構色瘩,而 Any?
在頂層,Nothing
在底層逸寓,以及非空類型是對應可空類型的子類型居兆。就這么多了,沒有其它特殊規(guī)則竹伸。一些有用語言特性泥栖,如空安全性、面向對象多態(tài)性勋篓、不可達代碼分析都是由這些簡單吧享,可預測的規(guī)則引起的。得益于這種一致性譬嚣,Kotlin 的類型檢查器是一個強有力的工具钢颂,可以幫助你編寫簡潔正確的程序。
<a id="neat">1.</a> “很棒(neat)” 的意思是 “優(yōu)雅巧妙高效”孤荣,而不是凱文 · 科斯特納在麥當娜的舞臺上表現(xiàn)出的含義<a id="neat"> </a>