Kotlin 中的繼承模式與 Java 有一些不同之處,主要在三個地方:
Kotlin 中所有類的最終父類是 Any,而非 Java 中的 Object;
Kotlin 中 非抽象類默認不可繼承;
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. 覆蓋屬性時的細節
在覆蓋屬性時,有兩點需要特別注意:
不允許用 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 方法
可以在主構造函數中定義要覆蓋的屬性:
open class Person(open val name: String)class Student(override val name: String): Person(name)
如上,我們在 Person 類的主構造函數中將 name 定義爲 open 的屬性,然後在 Student 類中使用 override 覆蓋了它。