Dive Into Kotlin(四):為什麼 Kotlin 的根型別是「Any?」

ScalaCool發表於2018-12-19

本文由 Prefert 發表在 ScalaCool 團隊部落格。

我們在Dive Into Kotlin(二):Kotlin 型別結構設計中已經對Kotlin的型別系統進行過大致的介紹。

文中提到過: Any 型別是 Kotlin 中 所有非空型別(ex: String, Int) 的根型別。

當我們需要和 Java 互操作的時候,Kotlin 把 Java 方法引數和返回型別中用到的 Object 型別看作 Any(更確切地說是當做「平臺型別」)。當 Kotlin 函式中使用 Any 時,它會被編譯成 Java 位元組碼中的 Object

什麼是平臺型別?

平臺型別本質上就是 Kotlin 不知道可空性資訊的型別—所有 Java 引用型別在 Kotlin 中都表現為平臺型別。當在 Kotlin 中處理平臺型別的值的時候,它既可以被當做可空型別來處理,也可以被當做非空型別來操作。

平臺型別的引入是 Kotlin 相容 Java 時的一種權衡設計。試想下,如果所有來自 Java 的值都被看成非空,那麼就容易寫出比較危險的程式碼。反之,如果 Java 值都強制當做可空,則會導致大量的 null 檢查。綜合考量,平臺型別是一種折中的設計方案。

在 Java 中,Object 型別位於其型別系統的頂級。如果說 Kotlin 與 Java 是100%相容的,那我們是否可以說 Any 也是Kotlin的所有型別的頂級型別呢?在上一篇文章中,這個問題同樣困擾了我,官方也並沒有做出一個明確的說明。但是我們可以注意到的是, Kotlin 中引入了「可空型別」這個概念,這很可能會對系統層級結構產生影響。

在探索根型別之前,先讓我們理清兩個概念:繼承(Inheriting)和 子型別化(Subtyping)。

繼承和子型別化的區別

這是一個看似容易實則不簡單的問題:到底什麼才是子型別化( Subtyping )?我們曾在Subtyping vs Typeclasses(一)這篇部落格討論過這個問題。如果你只有 Java 這門程式語言的開發經驗,很容易陷入一個誤區——繼承關係決定父子型別關係。因為在 Java 中, 類與型別大部分情況下都是「等價」的(在 Java 泛型出現前)。

事實上,「繼承」和「子型別化」是兩個完全不同的概念。

  • 子型別化的核心是一種型別的替代關係(我們也可以稱之為子型別多型),通常可表示為:

    S <: T
    複製程式碼

    以上 ST 的子類,這意味著在需要 T 型別值的地方,S 型別的值同樣適用,如在 Kotlin 中 IntNumber 的子類:

    fun printNum(num: Number) {
      println(num)
    }
    
    >>> val n: Int = 1
    >>> printNum(n)
    
    >>> 1
    >>> printNum("I am a String")
    error: type mismatch: inferred type is String but Number was expected
    複製程式碼
  • 相對而言,繼承強調的是一種「實現上的複用」,而子型別化是一種型別語義的關係,與實現沒關係。在 Java 中,我們似乎也可以通過類繼承來實現上述關係:

    class S extends class T
    複製程式碼

    由於在宣告父子型別關係的同時,也宣告瞭繼承的關係,所以通常會造成了某種程度上的混淆,但是這並不能說明這兩個概念就是等價的。

雖然 AnyAny? 看起來沒有繼承關係,然而當我們在需要用 Any? 型別值的地方,顯然可以傳入一個型別為 Any 的值,這在編譯上不會產生問題。反之卻行不通,比如:一個引數型別為 Any 的函式,我們傳入符合 Any? 型別的 null 值,就會出現如下的錯誤:

error: null can not be a value of a non-null type Any
複製程式碼

以上,我們也可以初步得出結論:Any的值能在所有情況下代替 Any? 的值,這符合「子型別化」的概念。

因此,我們可以很大膽地說:在Kotlin的型別系統中,Any?Any 的父型別,而且是所有型別的根型別,雖然當前的 Kotlin 官網文件沒有介紹過這一點。

Any? 與 Any??

一個你可能會挑戰的問題是,如果 Any?Any 的父型別,那麼 Any?? 是否又是 Any? 的父型別, 如果成立,那麼是否意味著就沒有所謂的「所有型別的根型別」了?

其實,Kotlin 中的可空型別可以看做所謂的 Union Type,在程式中通常用 A | B 表示,近似於數學中的並集。如果用型別的並集來表示 Any? ,可寫為 Any ∪ Null。相應的 Any?? 就表示為 Any ∪ Null ∪ Null,這等價於 Any ∪ Null, 即 Any?? 等價於 Any? 。因此,說 Any? 是所有型別的根型別是沒有問題的。

Dive Into Kotlin(四):為什麼 Kotlin 的根型別是「Any?」

相關文章