Kotlin 的類型層次結(jié)構(gòu)需要學(xué)習(xí)的規(guī)則很少。這些規(guī)則一致且可預(yù)測(cè)地結(jié)合在一起。由于這些規(guī)則听皿,Kotlin 可以提供有用的、用戶可擴(kuò)展的語(yǔ)言特性——空安全宽档、多態(tài)性和無(wú)法訪問(wèn)的代碼分析——而無(wú)需在編譯器和 IDE 中求助于特殊情況和臨時(shí)檢查尉姨。
從頂部開(kāi)始
所有類型的 Kotlin 對(duì)象都被組織成子類型/超類型關(guān)系的層次結(jié)構(gòu)。
在該層次結(jié)構(gòu)的“頂部”是抽象類Any吗冤。例如又厉,類型 String 和 Int 都是Any.
Any相當(dāng)于Java的Object類。與 Java 不同欣孤,Kotlin 沒(méi)有區(qū)分語(yǔ)言固有的“原始”類型和用戶定義的類型馋没。它們都是同一類型層次結(jié)構(gòu)的一部分。
如果您定義的類不是從另一個(gè)類顯式派生的降传,則該類將是 Any 的直接子類型。
class Fruit(val ripeness: Double)
如果確實(shí)為用戶定義的類指定了基類勾怒,則基類將是新類的直接超類型婆排,但該類的最終祖先將是 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)
如果您的類實(shí)現(xiàn)了一個(gè)或多個(gè)接口笔链,它將具有多個(gè)直接超類型段只,其中 Any 作為最終祖先。
interface ICanGoInASalad
interface ICanBeSunDried
class Tomato(ripeness: Double):
Fruit(ripeness),
ICanGoInASalad,
ICanBeSunDried
Kotlin 類型檢查器強(qiáng)制執(zhí)行子類型/超類型關(guān)系鉴扫。
例如赞枕,您可以將子類型存儲(chǔ)到超類型變量中:
var f: Fruit = Banana(bendiness=0.5)
f = Peach(fuzziness=0.8)
但是您不能將超類型值存儲(chǔ)到子類型變量中:
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
可空類型
與 Java 不同,Kotlin 區(qū)分“非空”和“可空”類型坪创。到目前為止我們看到的類型都是“非空”的炕婶。Kotlin 不允許null用作這些類型的值。您可以保證取消引用對(duì)“非空”類型值的引用永遠(yuǎn)不會(huì)拋出 NullPointerException莱预。
類型檢查器拒絕嘗試使用 null 或期望非 null 類型的可為 null 類型的代碼柠掂。
例如:
var s : String = null
// Error: Null can not be a value of a non-null type String
如果您希望某個(gè)值可能為空,則需要使用該值類型的可空等價(jià)物依沮,由后綴 '?' 表示涯贞。例如,該類型String?是可空等價(jià)的String危喉,因此允許所有字符串值加上空值宋渔。
var s : String? = null
s = "foo"
s = null
s = bar
類型檢查器確保您永遠(yuǎn)不會(huì)在沒(méi)有首先測(cè)試它不為空的情況下使用可空值。Kotlin 提供了操作符來(lái)使處理可為空類型更加方便辜限。有關(guān)示例皇拣,請(qǐng)參閱Kotlin 語(yǔ)言參考的Null Safety 部分。
當(dāng)非空類型通過(guò)子類型關(guān)聯(lián)時(shí)列粪,它們的可空等價(jià)物也以相同的方式關(guān)聯(lián)审磁。例如谈飒,因?yàn)镾tring是 的子類型Any,String?是 的子類型Any?态蒂,因?yàn)锽anana是 的子類型Fruit杭措,Banana?是 的子類型Fruit?。
正如Any非空類型層次結(jié)構(gòu)Any?的根一樣钾恢, 是可為空類型層次結(jié)構(gòu)的根手素。因?yàn)锳ny?是Any 的超類型,Any?是 Kotlin 類型層次結(jié)構(gòu)的最頂層瘩蚪。
非空類型是其可空等價(jià)物的子類型泉懦。例如,String作為Any的子類型疹瘦,也是String?的子類型崩哩。
這就是為什么您可以將非空字符串值存儲(chǔ)到可為空字符串中的原因,但非空字符串變量不能存儲(chǔ)可為空的字符串言沐。Kotlin 的空安全性不是由特殊規(guī)則強(qiáng)制執(zhí)行的邓嘹,而是適用于非空類型之間的相同子類型/超類型規(guī)則的結(jié)果。
這也適用于用戶定義的類型層次結(jié)構(gòu)险胰。
Unit
Kotlin 是一種面向表達(dá)式的語(yǔ)言汹押。所有控制流語(yǔ)句(除了變量賦值,異常情況下)都是表達(dá)式起便。Kotlin 沒(méi)有像 Java 和 C 那樣的 void 函數(shù)棚贾。函數(shù)總是返回一個(gè)值。實(shí)際上不計(jì)算任何東西的函數(shù)——例如榆综,因?yàn)樗鼈兊母弊饔枚徽{(diào)用—— return Unit妙痹,一種具有單個(gè)值的類型,也稱為Unit.
大多數(shù)情況下奖年,您不需要顯式指定 Unit 作為返回類型或從函數(shù)返回 Unit细诸。如果你寫了一個(gè)帶有塊體的函數(shù)并且沒(méi)有指定結(jié)果類型,編譯器會(huì)把它當(dāng)作一個(gè)單元函數(shù)陋守。如果編寫單表達(dá)式函數(shù)震贵,編譯器可以推斷 Unit 返回類型,就像任何其他類型一樣水评。
fun example1() {
println("block body and no explicit return type, so returns Unit")
}
val u1: Unit = example1()
fun example2() =
println("single-expression function for which the compiler infers the return type as Unit")
val u2: Unit = example2()
沒(méi)什么特別的Unit猩系。像任何其他類型一樣,它是Any中燥, 它可以為空寇甸,因此是Unit?的子類型,它是Any?的子類型。
類型Unit?是一個(gè)奇怪的小邊緣情況拿霉,是 Kotlin 類型系統(tǒng)一致性的結(jié)果吟秩。它只有兩個(gè)成員:Unit值和null。我從來(lái)沒(méi)有發(fā)現(xiàn)需要明確地使用它绽淘,但是類型系統(tǒng)中沒(méi)有“void”的特殊情況這一事實(shí)使得通用地處理所有類型的函數(shù)變得更加容易涵防。
Nothing
在 Kotlin 類型層次結(jié)構(gòu)的最底層是 Nothing類型。
顧名思義沪铭,Nothing 是一種沒(méi)有實(shí)例的類型壮池。類型為 Nothing 的表達(dá)式不會(huì)產(chǎn)生值。
請(qǐng)注意 Unit 和 Nothing 之間的區(qū)別杀怠。表達(dá)式類型 Unit 的計(jì)算結(jié)果為單例值Unit椰憋。對(duì)類型為 Nothing 的表達(dá)式的求值根本不會(huì)返回。
這意味著任何跟在 Nothing 類型表達(dá)式后面的代碼都是不可訪問(wèn)的赔退。編譯器和 IDE 會(huì)警告您此類無(wú)法訪問(wèn)的代碼橙依。
什么樣的表達(dá)式計(jì)算為Nothing?那些執(zhí)行控制流的离钝。
例如票编,throw關(guān)鍵字中斷表達(dá)式的計(jì)算并從封閉函數(shù)中拋出異常。因此卵渴,throw 是 Nothing 類型的表達(dá)式。
通過(guò)將 Nothing 作為所有其他類型的子類型鲤竹,類型系統(tǒng)允許程序中的任何表達(dá)式實(shí)際上無(wú)法計(jì)算值浪读。這模擬了現(xiàn)實(shí)世界的可能性,例如 JVM 在計(jì)算表達(dá)式時(shí)內(nèi)存不足辛藻,或者有人拔掉了計(jì)算機(jī)的電源插頭碘橘。這也意味著我們可以從任何表達(dá)式中拋出異常。
fun formatCell(value: Double): String =
if (value.isNaN())
throw IllegalArgumentException("$value is not a number")
else
value.toString()
得知該return語(yǔ)句的類型為 Nothing 時(shí)可能會(huì)感到驚訝吱肌。Return 是一個(gè)控制流語(yǔ)句痘拆,它立即從封閉函數(shù)返回一個(gè)值,中斷對(duì)它所屬的任何表達(dá)式的求值氮墨。
fun formatCellRounded(value: Double): String =
val rounded: Long = if (value.isNaN()) return "#ERROR" else Math.round(value)
rounded.toString()
進(jìn)入無(wú)限循環(huán)或終止當(dāng)前進(jìn)程的函數(shù)的結(jié)果類型為 Nothing纺蛆。例如,Kotlin 標(biāo)準(zhǔn)庫(kù)將exitProcess函數(shù)聲明為:
fun exitProcess(status: Int): Nothing
如果您編寫自己的返回 Nothing 的函數(shù)规揪,編譯器將在調(diào)用您的函數(shù)后檢查無(wú)法訪問(wèn)的代碼桥氏,就像使用內(nèi)置控制流語(yǔ)句一樣。
inline fun forever(action: ()->Unit): Nothing {
while(true) action()
}
fun example() {
forever {
println("doing...")
}
println("done") // Warning: Unreachable code
}
與空安全一樣猛铅,無(wú)法訪問(wèn)的代碼分析不是通過(guò) IDE 和編譯器中的臨時(shí)字支、特殊情況檢查來(lái)實(shí)現(xiàn)的,因?yàn)樗仨氃?Java 中進(jìn)行。這是類型系統(tǒng)的一個(gè)函數(shù)堕伪。
什么都可以為空揖庄?
Nothing,與任何其他類型一樣欠雌,可以設(shè)為可為空蹄梢,并給出類型Nothing?。 Nothing?可以只包含一個(gè)值:null桨昙。事實(shí)上检号,Nothing? 是null的類型。
Nothing?是所有可空類型的最終子類型蛙酪,它允許將該值null用作任何可空類型的值齐苛。
結(jié)論
當(dāng)您一次性考慮所有這些時(shí),Kotlin 的整個(gè)類型層次結(jié)構(gòu)可能會(huì)感覺(jué)非常復(fù)雜桂塞。
但永遠(yuǎn)不要害怕凹蜂!
我希望這篇文章已經(jīng)證明 Kotlin 有一個(gè)簡(jiǎn)單且一致的類型系統(tǒng)。需要學(xué)習(xí)的規(guī)則很少:Any?頂部和Nothing底部的超類型/子類型關(guān)系的層次結(jié)構(gòu)阁危,以及非空類型和可空類型之間的子類型關(guān)系玛痊。就是這樣。沒(méi)有特殊情況狂打±奚罚空安全、面向?qū)ο蟮亩鄳B(tài)性和無(wú)法訪問(wèn)的代碼分析等有用的語(yǔ)言功能都源于這些簡(jiǎn)單趴乡、可預(yù)測(cè)的規(guī)則对省。由于這種一致性,Kotlin 的類型檢查器是一個(gè)強(qiáng)大的工具晾捏,可以幫助您編寫簡(jiǎn)潔蒿涎、正確的程序。
英文好的同學(xué)可以直接閱讀原文