Kotlin 類型層次結(jié)構(gòu)

Kotlin 的類型層次結(jié)構(gòu)需要學(xué)習(xí)的規(guī)則很少。這些規(guī)則一致且可預(yù)測地結(jié)合在一起。由于這些規(guī)則,Kotlin 可以提供有用的、用戶可擴(kuò)展的語言特性——空安全、多態(tài)性和無法訪問的代碼分析——而無需在編譯器和 IDE 中求助于特殊情況和臨時(shí)檢查。

從頂部開始

所有類型的 Kotlin 對象都被組織成子類型/超類型關(guān)系的層次結(jié)構(gòu)。

在該層次結(jié)構(gòu)的“頂部”是抽象類Any。例如,類型 String 和 Int 都是Any.

image.png

Any相當(dāng)于Java的Object類。與 Java 不同,Kotlin 沒有區(qū)分語言固有的“原始”類型和用戶定義的類型。它們都是同一類型層次結(jié)構(gòu)的一部分。

如果您定義的類不是從另一個(gè)類顯式派生的,則該類將是 Any 的直接子類型。

class Fruit(val ripeness: Double)
image.png

如果確實(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)
image.png

如果您的類實(shí)現(xiàn)了一個(gè)或多個(gè)接口,它將具有多個(gè)直接超類型,其中 Any 作為最終祖先。

interface ICanGoInASalad
interface ICanBeSunDried

class Tomato(ripeness: Double): 
    Fruit(ripeness), 
    ICanGoInASalad, 
    ICanBeSunDried
image.png

Kotlin 類型檢查器強(qiáng)制執(zhí)行子類型/超類型關(guān)系。

例如,您可以將子類型存儲到超類型變量中:

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

可空類型

與 Java 不同,Kotlin 區(qū)分“非空”和“可空”類型。到目前為止我們看到的類型都是“非空”的。Kotlin 不允許null用作這些類型的值。您可以保證取消引用對“非空”類型值的引用永遠(yuǎn)不會拋出 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)不會在沒有首先測試它不為空的情況下使用可空值。Kotlin 提供了操作符來使處理可為空類型更加方便。有關(guān)示例,請參閱Kotlin 語言參考的Null Safety 部分

當(dāng)非空類型通過子類型關(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)的最頂層。

image.png

非空類型是其可空等價(jià)物的子類型。例如,String作為Any的子類型,也是String?的子類型。

image.png

這就是為什么您可以將非空字符串值存儲到可為空字符串中的原因,但非空字符串變量不能存儲可為空的字符串。Kotlin 的空安全性不是由特殊規(guī)則強(qiáng)制執(zhí)行的,而是適用于非空類型之間的相同子類型/超類型規(guī)則的結(jié)果。

這也適用于用戶定義的類型層次結(jié)構(gòu)。

image.png

Unit

Kotlin 是一種面向表達(dá)式的語言。所有控制流語句(除了變量賦值,異常情況下)都是表達(dá)式。Kotlin 沒有像 Java 和 C 那樣的 void 函數(shù)。函數(shù)總是返回一個(gè)值。實(shí)際上不計(jì)算任何東西的函數(shù)——例如,因?yàn)樗鼈兊母弊饔枚徽{(diào)用—— return Unit,一種具有單個(gè)值的類型,也稱為Unit.

大多數(shù)情況下,您不需要顯式指定 Unit 作為返回類型或從函數(shù)返回 Unit。如果你寫了一個(gè)帶有塊體的函數(shù)并且沒有指定結(jié)果類型,編譯器會把它當(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()

沒什么特別的Unit。像任何其他類型一樣,它是Any, 它可以為空,因此是Unit?的子類型,它是Any?的子類型。

image.png

類型Unit?是一個(gè)奇怪的小邊緣情況,是 Kotlin 類型系統(tǒng)一致性的結(jié)果。它只有兩個(gè)成員:Unit值和null。我從來沒有發(fā)現(xiàn)需要明確地使用它,但是類型系統(tǒng)中沒有“void”的特殊情況這一事實(shí)使得通用地處理所有類型的函數(shù)變得更加容易。

Nothing

在 Kotlin 類型層次結(jié)構(gòu)的最底層是 Nothing類型。

image.png

顧名思義,Nothing 是一種沒有實(shí)例的類型。類型為 Nothing 的表達(dá)式不會產(chǎn)生值。

請注意 Unit 和 Nothing 之間的區(qū)別。表達(dá)式類型 Unit 的計(jì)算結(jié)果為單例值Unit。對類型為 Nothing 的表達(dá)式的求值根本不會返回。

這意味著任何跟在 Nothing 類型表達(dá)式后面的代碼都是不可訪問的。編譯器和 IDE 會警告您此類無法訪問的代碼。

什么樣的表達(dá)式計(jì)算為Nothing?那些執(zhí)行控制流的。

例如,throw關(guān)鍵字中斷表達(dá)式的計(jì)算并從封閉函數(shù)中拋出異常。因此,throw 是 Nothing 類型的表達(dá)式。

通過將 Nothing 作為所有其他類型的子類型,類型系統(tǒng)允許程序中的任何表達(dá)式實(shí)際上無法計(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語句的類型為 Nothing 時(shí)可能會感到驚訝。Return 是一個(gè)控制流語句,它立即從封閉函數(shù)返回一個(gè)值,中斷對它所屬的任何表達(dá)式的求值。

fun formatCellRounded(value: Double): String =
    val rounded: Long = if (value.isNaN()) return "#ERROR" else Math.round(value)
    rounded.toString()

進(jìn)入無限循環(huán)或終止當(dāng)前進(jìn)程的函數(shù)的結(jié)果類型為 Nothing。例如,Kotlin 標(biāo)準(zhǔn)庫將exitProcess函數(shù)聲明為:

fun exitProcess(status: Int): Nothing

如果您編寫自己的返回 Nothing 的函數(shù),編譯器將在調(diào)用您的函數(shù)后檢查無法訪問的代碼,就像使用內(nèi)置控制流語句一樣。

inline fun forever(action: ()->Unit): Nothing {
    while(true) action()
}

fun example() {
    forever {
        println("doing...")
    }
    println("done") // Warning: Unreachable code
}

與空安全一樣,無法訪問的代碼分析不是通過 IDE 和編譯器中的臨時(shí)、特殊情況檢查來實(shí)現(xiàn)的,因?yàn)樗仨氃?Java 中進(jìn)行。這是類型系統(tǒng)的一個(gè)函數(shù)。

什么都可以為空?

Nothing,與任何其他類型一樣,可以設(shè)為可為空,并給出類型Nothing?。 Nothing?可以只包含一個(gè)值:null。事實(shí)上,Nothing? 是null的類型。

Nothing?是所有可空類型的最終子類型,它允許將該值null用作任何可空類型的值。

image.png

結(jié)論

當(dāng)您一次性考慮所有這些時(shí),Kotlin 的整個(gè)類型層次結(jié)構(gòu)可能會感覺非常復(fù)雜。

image.png

但永遠(yuǎn)不要害怕!

我希望這篇文章已經(jīng)證明 Kotlin 有一個(gè)簡單且一致的類型系統(tǒng)。需要學(xué)習(xí)的規(guī)則很少:Any?頂部和Nothing底部的超類型/子類型關(guān)系的層次結(jié)構(gòu),以及非空類型和可空類型之間的子類型關(guān)系。就是這樣。沒有特殊情況??瞻踩?、面向?qū)ο蟮亩鄳B(tài)性和無法訪問的代碼分析等有用的語言功能都源于這些簡單、可預(yù)測的規(guī)則。由于這種一致性,Kotlin 的類型檢查器是一個(gè)強(qiáng)大的工具,可以幫助您編寫簡潔、正確的程序。

英文好的同學(xué)可以直接閱讀原文

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容