面向對象:繼承

Kotlin 中的繼承模式與 Java 有一些不同之處,主要在三個地方:

  1. Kotlin 中所有類的最終父類是 Any,而非 Java 中的 Object;

  2. Kotlin 中 非抽象類默認不可繼承

  3. Kotlin 中 非抽象類函數和類屬性默認不可覆蓋

1. open 關鍵字和 override 關鍵字

open 關鍵字在 Kotlin 中可以用在定義 非抽象的類、類函數和類屬性 之前,用來將它們標記爲可繼承 的:

open class Person(val name: String) {
    open var age = 0
    open fun say() = "My name is $name, $age years old."}

override 關鍵字用在定義 子類覆蓋父類函數和屬性 之前,用來標記覆蓋了父類的函數和屬性:

class Student(name: String) : Person(name) {
    override var age = 0
    override fun say() = "I'm a student named $name, $age years old."}

需要注意的是,如果類不是 open 的,那麼它內部的屬性和函數都不能是 open 的。理由很簡單,不會被繼承的類,不可能有屬性和函數被覆蓋。

雖然 Kotlin 默認沒有 open 的屬性和函數不能被覆蓋,但實際上使用 override 標記和屬性和函數可以被覆蓋,如果一個 open 類不想讓子類覆蓋自己 override 的屬性和函數,可以使用 final override 來覆蓋,這樣就可以避免再次被子類覆蓋了。

2. 繼承類的寫法和構造方法

Kotlin 中沒有 extends 關鍵字,聲明一個類繼承自另一個標記爲 open 的類的方法是:

class 子類[(主構造方法參數)]: 父類[(主構造方法參數)] {……}

可以這樣理解,冒號 : 在 Kotlin 中表示前者屬於後者類型,比如我們定義變量時用冒號聲明變量的類型,定義函數時用冒號聲明函數的返回類型。而在繼承中,子類實際上就屬於父類的類型,所以沒有必要再專門用一個 extends 關鍵字聲明父類,只需要用冒號。

Java 中,子類在調用自身的構造方法前,會先調用父類的構造方法,所以如果父類有自定義的構造方法,子類就必須顯式地定義構造方法,並在構造方法中顯式調用父類的構造方法。Kotlin 也是如此,如果父類定義了主構造函數,子類就必須顯式地調用父類的主構造函數,原因與 Java 一樣。

所以上面 Student 類在定義時,不能這樣寫:

// 編譯錯誤,Person 類有自定義的主構造函數class Student(name: String): Person {……}

3. 覆蓋屬性時的細節

在覆蓋屬性時,有兩點需要特別注意:

  1. 不允許用 val 屬性覆蓋 var 屬性。用 val 屬性覆蓋 val 屬性和用 var 屬性覆蓋 var 屬性很好理解,該是什麼樣還是什麼樣嘛。此外,Kotlin 還允許用 var 屬性覆蓋 val 屬性(只需給子類中的屬性添加一個 setter 方法),但不允許用 val 屬性覆蓋 var 屬性:

    open class Person() {
      var name = ""}class Student(): Person(name) {
      override val name: String = name}

    當我們使用多態時:

    val alex: Person = Student()alex.name = "Alex" // 錯誤,子類對象的屬性沒有 setter 方法

  2. 可以在主構造函數中定義要覆蓋的屬性

    open class Person(open val name: String)class Student(override val name: String): Person(name)

    如上,我們在 Person 類的主構造函數中將 name 定義爲 open 的屬性,然後在 Student 類中使用 override 覆蓋了它。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章