首頁>技術>

繼承是面向物件程式設計的三大特性之一,在開發過程中會經常使用,繼承可以讓子類擁有父類的功能,也可以對父類功能進行增強修改。

面向物件程式設計的三大特性:封裝、繼承、多型

繼承普通類

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
繼承(實現)抽象類(介面)

相比於普通類,抽象類與介面就比較"開放"了,因為它們本身設計出來就是為了讓子類(實現類)去繼承(實現)的:

介面、介面方法、抽象類預設為 open
abstract 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)    }}

8
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 聊聊gost的HashSet