繼承是面向物件程式設計的三大特性之一,在開發過程中會經常使用,繼承可以讓子類擁有父類的功能,也可以對父類功能進行增強修改。
面向物件程式設計的三大特性:封裝、繼承、多型
繼承普通類Kotlin 中預設 類、方法 都是 final 的,因此預設無法被子類繼承或重寫,但可透過 open 關鍵字來解除:
父類需要 open 才可以被繼承父類方法、屬性需要 open 才可以被覆寫覆寫父類成員需要 override 關鍵字open class Person() { open fun walk() { println("慢慢走") }}class Doctor : Person() { override fun walk() { // super.walk() println("快走") }}
子類重寫父類方法時,會使用到 override 關鍵字,另外,重寫的方法體中可以使用 super.xxx() 來呼叫父類方法中原有的邏輯,當然你也可以選擇完全重寫(方法體中不使用 super.xxx() 即可)。Kotlin 中父類的屬性與方法一樣,子類要重寫的話,需要使用 open 關鍵字修飾父類屬性,解除 final:
// 重寫父類屬性open class Person(open val name: String) { ...}class Doctor(override val name: String) : Person(name) { ...}
當然啦,也不是所有子類都需要重寫父類成員屬性的,因為子類可透過 getter/setter 對父類的成員屬性進行訪問。這時子類僅需要將臨時變數(即不使用 val 或 var 修飾的構造器引數變數)傳遞到父類構造器中就好了:
繼承類時實際上是呼叫了父類構造方法// 傳遞構造引數open class Person(val name: String) { // 使用了val,所以name是父類Person的成員屬性。 ...}class Doctor(name: String) : Person(name) { // 沒有val,所以name僅僅只是一個臨時變數。 ...}// 不想重寫父親屬性就不要使用val或var修改,因為這相當於在子類中定義了一個與父類成員屬性同名的屬性。// class Doctor(val name: String) : Person(name) // IDE報錯:'name' hides memeber of supertype 'Person' and needs 'override' modifier
繼承(實現)抽象類(介面)相比於普通類,抽象類與介面就比較"開放"了,因為它們本身設計出來就是為了讓子類(實現類)去繼承(實現)的:
介面、介面方法、抽象類預設為 openabstract class Person() { open fun walk() { // 成員方法,想被子類重寫需要使用open關鍵字 println("慢慢走") } abstract fun work() // 抽象方法,預設就是open的}class Doctor : Person() { override fun walk() { println("快走") } override fun work() { // 子類實現父類抽象方法,還是需要使用override關鍵字 }}
介面代理
Kotlin 不支援多繼承,只能使用介面來實現多個功能,但我們又不想在類中將介面方法的實現寫死,這樣程式碼就不夠靈活,因此,往往會在類的構造器引數中,將介面的實現類傳進來,而在我們的類中,只需要在實現了介面方法的方法體中呼叫實現類的對應方法:
interface Driver { fun dirve()}class Manager(val driver: Driver) : Driver { override fun dirve() { driver.dirve() }}
但這種寫法會顯得比較羅嗦,Kotlin 提供了 介面代理 將介面方法實現直接交給代理類實現:
格式: class XXX(介面實現類) : 介面 by 介面實現類class Manager(driver: Driver): Driver by driver
這算不算是對 Kotlin 不支援多繼承的 "曲線救國" 呢?其實 Java 也一樣不支援多繼承,通常會使用組合的方式來處理。
介面方法衝突前面我們已經知道了介面方法是可以有預設實現的,Kotlin 可以實現多個介面,而這些介面可能擁有相同的介面方法,就比如這樣:
interface Radio { fun display(): String { return "107.1" } ...}interface Compass { fun display(): String { return "東方" } ...}
這時有一個實現類,同時實現了上述兩個介面,就會出現介面方法衝突問題:
class Phone(val type: Int) : Radio, Compass { override fun display(): String { return super.display() // IDE報錯:Many supertypes available, please specify the one you mean in angle brackets, e.g. 'super<Foo>' } ...}
根據提示,這個介面方法衝突問題其實也挺好解決的,只需要在 super 後使用泛型指定父類(介面)名即可:
class Phone(val type: Int) : Radio, Compass { override fun display(): String { when (type) { 0 -> return super<Radio>.display() 1 -> return super<Compass>.display() else -> return "不支援" } }}
要注意,這裡的介面方法衝突,指的是 簽名一致且返回值相同的衝突 ,如果簽名一致但返回值型別不同,那這個問題將無解:
方法簽名一致指的是方法名、引數列表相同。
interface Radio { fun display(): String { return "107.1" }}interface Compass { fun display(): Int { return 180 }}// IDE報錯:// Platform declaration clash: The following declarations have the same JVM signature (display()Ljava/lang/String;)// Platform declaration clash: The following declarations have the same JVM signature (display()I;)class Phone(val type: Int) : Radio, Compass { override fun display(): String { // IDE報錯 } override fun display(): Int { // IDE報錯 }}
但反過來,如果方法返回值相同但簽名不一致,那這情況就不一樣了,就相當於 2 個不一樣的方法,需要分開重寫,只是在使用 super.xxx() 呼叫介面方法時同樣還是需要使用泛型來指明父介面,避免發生歧義:
interface Radio { fun display(): String { return "107.1" }}interface Compass { fun display(i: Int): Int { return i }}class Phone(val type: Int) : Radio, Compass { override fun display(): String { return super <Radio>.display() } override fun display(i: Int): Int { return super<Compass>.display(i) }}