iT邦幫忙

2022 iThome 鐵人賽

DAY 22
0

概念

延續昨天類別之後,繼承是指類別與類別之間的關係,相關類別可以透過繼承來共享資料及行為。
我們可以舉生活上的例子來解釋繼承的概念,我們常常會將類似的事物分在同一類,比如:公車、跑車、休旅車、轎車雖然都外型不同、用途也有些差異,不過他們因為都還是具備車輛的基本特徵,比如都有品牌、乘載人數、車牌、具有交通、運輸用途...等等共同特徵,所以可以將他們都歸類為「車輛」。又比如說建築這個概念,可以是稻草屋、木屋、磚屋、鐵皮屋、糖果屋...等不同材質,但同樣都會具有牆壁、門窗、屋頂、地板、房間等共同特徵,基本都具有容納人類、遮風避雨的功能(不論效果好壞)、糖果屋可能另外具有引誘孩童的功能(誤)。
如果沒有繼承概念的話,以車子來說,那些車種都必須各自定義一次共同的特徵及行為。
而繼承就是可以讓我們將希望共享的特徵或行為建立在一個父類別,比如車輛,然後再建立子類別跑車、貨車、公車去繼承父類別這些共同的東西,各自依需要去客製化。

建立繼承關係

關鍵字open

要建立繼承關係之前,必須先跟kotlin說我願意...被繼承,否則class是很矜持的,預設都是final,為封閉無法被繼承。
只有標明open關鍵字的類別才可以被繼承(open class:我準備好當爸爸了)
我們new一個新的class Car,使其可以被繼承,如:

open class Car

語法

來建立繼承關係先:

class 子類別名稱():父類別名稱(){}

Car類別加上open關鍵字使其可被繼承,然後在同一個檔案(因為是練習就不另開新檔案囉)中建立 Taxi 類別名稱後方加上:Car(),就是指繼承Car類別,成為其子類別,Car相對地成為Taxi的父類別(或超類別)。

//父類別Car
open class Car
//Car的子類別Bus
class Bus : Car(){}

先為父類別Car添加一些車輛會有的屬性跟函數:
簡單設置了車輛都要具有品牌、乘載量的屬性;發動、煞車的函數。

open class Car(val brand: String, val capacity: Int) {
    fun start() {
        println("車子發動了")
    }
    fun brake() {
        println("車子停下了")
    }
}

子類別 taxi可以繼承父類別這些已經設置好的屬性及函數:
父類別要求brandcapacity兩個引數,於是下面這個寫法就是將子類別兩個參數taxiBrandtaxiCapacity傳給父類別Car的建構函數。這樣的寫法,在Taxi實例化時才設定兩個引數。

//將taxiBrand、taxiCapacity傳給父類別Car的建構函數
class Taxi(taxiBrand: String, taxiCapacity: Int) : Car(taxiBrand, taxiCapacity) { }

//實例化一台taxi
fun main(){
    val taxi = Taxi("Nissan",4) 
}

當然也可以都先預設好或是預設其中一個,比如說車隊都是Toyota的車車:

class Taxi(taxiCapacity: Int) : Car("Toyota", taxiCapacity)

//實例化一台預設為Toyota的7人座taxi
fun main(){
    val taxi = Taxi(7) 
}

子類別透過句點運算子「.」使用父類別設好的函數,子類別不必再重新定義,如下:

taxi.start()  //結果 : 車子發動了

若是需要特別為Taxi類別客製化 start() ,我們可以使用override關鍵字來覆寫,在覆寫父類別函數之前記得先把父類別的函數加上open關鍵字喔!!


open class Car(var brand: String, var capacity: Int) {
   open fun start() {
        println("車子發動了")
    }
   //不加open關鍵字是無法覆寫這個brake()函數的
    fun brake() {
        println("車子停下了")
    }
}


class Taxi(taxiBrand: String, taxiCapacity: Int) : Car(taxiBrand, taxiCapacity) {
    //覆寫start函數
    override fun start() {
    // 把這行槓掉,super是指父類Car,留著的話就等於呼叫一次父類的start()函數
    //  super.start()    
        println("計程車發動!")  //覆寫的新內容
    }
}

如果硬要覆寫brake()會出現下圖這樣的錯誤警告:
依照提示將open加上就沒問題囉。

子類別自訂新的屬性及函數

除了覆寫外,當然也是可以自訂不同於父類別的新屬性及函數,比如說計程車都有起跳金額、也要跳表計費的功能:

  • protected可見性修飾符 : 僅類別及他的子類別可見
open class Taxi(
    protected open val initialCharge: Int,
    taxiBrand:String,
    taxiCapacity: Int
) : Car(taxiBrand, taxiCapacity) {
    override fun start() {...}
    //新增一跳表計費功能:每次計費前要先發動車子再開始跳表
    fun chargeByMeter() {
        start()
        println("起跳金額:$initialCharge 元,現在開始跳表計費...")
    }
}

//實例化
fun main() {
    val taxi = Taxi(70, "Toyota",4)
    taxi.chargeByMeter()
    // 結果:
    // 計程車發動!
    // 起跳金額:70 元,現在開始跳表計費...
}

那假設現在推出一種新的豪華計程車Luxury Taxi,因為車款是高級車款,所以起跳價較高,但本質上仍是一種Taxi,我們可以考慮讓它繼承Taxi而不是Car,因為它有更接近Luxury Taxi需求的屬性跟函數(如起跳金額跟計費函數),並且為了要吸引目標客群,它也額外提供特殊功能的車種 :

class LuxuryTaxi(val special: String, initial: Int, brand: String, capacity: Int) : Taxi(initial, brand, capacity) {
        fun specialServe(){
            println("本車提供特別功能 : $special")
        }
}

//實例化
fun main() {
    val luxuryTaxi = LuxuryTaxi("bulletproof" , 500 , "Benz", 4)
    luxuryTaxi.chargeByMeter()
    luxuryTaxi.specialServe()
    //結果: 
    //計程車發動!
    //起跳金額:500 元,現在開始跳表計費...
    //本車提供特別功能 : bulletproof
}

類型檢查

現在我們有三個類別,雖然顯而易見,但是可以玩一下類型檢測,可以讓我們更理解繼承關係中父、子類別之間的糾葛:

  • is 運算子 : 可以檢測該物件是否為某類型,會返回布林值,符合為true,不符合false。
    kotlin文檔-類型檢查
// is 的用法 : 
物件 is 類型    // 物件跟類別的位置不可對調

準備三位物件選手 :

    val car = Car(...)
    val taxi= Taxi(...)
    val luxuryTaxi:LuxuryTaxi = LuxuryTaxi(...)

    //類型檢查
    //luxuryTaxi類型檢查
    println(luxuryTaxi is LuxuryTaxi ) //true
    println(luxuryTaxi is Taxi )       //true
    println(luxuryTaxi is Car )        //true
    println(luxuryTaxi is Any)         //true
    
    //taxi類型檢查
    println(taxi is Taxi)               //true
    println(taxi is Car)                //true
    println(taxi is LuxuryTaxi)  //false
    println(taxi is Any)                //true
    
    //car類型檢查
    println(car is Car )                //true
    println(car is Taxi )       //false
    println(car is LuxuryTaxi ) //false
    println(car is Any)                 //true       

由這個檢測知道:

  • 子類別實例的類型既是自己的類別,也都會是父類別、父類別的父類別...。
  • 當類別沒有繼承父類別的時候,其實都是繼承Any類別,換句話說Any是所有類別的父類別。
  • 父類別的物件不會是子類別的類型。

需注意若使用條件判斷(如:when)檢測類型的話,分支順序將會影響結果,因為它是一分支順序檢測,然後返回第一個結果為true的分支。

小結

  • 類、函數、屬性都必須加上open關鍵字才可以被繼承
  • 繼承關係中被繼承的為父類別,繼承者為子類別。
  • Any為所有類別的父類別
  • 子類別會繼承父類別的屬性及函數,透過句點運算子「.」調用,視需要可覆寫(關鍵字override)父類別的屬性或函數。
  • 除了繼承來的屬性及函數,子類別也可以建立自己的屬性及函數。
  • 使用 is 運算子可以對物件做類型檢查。

今天就這樣,明天見~


上一篇
第21天 Kotlin小學堂(10) : Class 類別
下一篇
第23天 Kotlin小學堂(12) : 物件
系列文
新手向Android&Kotlin學習紀錄30天30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言