Kotlin 學習筆記 (11) — 繼承

Andy Lu
7 min readSep 16, 2020

物件導向的繼承特性用來定義類別之間的層級關係。

建立子類別 (Subclass)

一但子類別繼承某個類別,就會共用該類別的全部屬性。子類別繼承的類別,通常稱作父類別(parent class)或是超類別 (Superclass)

open 關鍵字

將類別加上 open 關鍵字,以開放給別的類別繼承。

open class Room(val name:String){
fun description() = "Room: $name"
fun load() = "Nothing much to see here..."
}

繼承

在子類別名稱後方利用冒號加上父類別。

class TownSquare: Room("Town Square")
  • 如此,TownSpuare 就會繼承 Room,但是兩個類別內容完全一樣(只有名稱不一樣)

覆寫 (Overridling)

子類別可以覆蓋屬性和行為,或者自己實作特有的屬性與行為。

class TownSquare: Room("Town Square"){
override fun load() = "The villagers rally and cheer as your enter!"
}
  • 被覆寫父類別的函數需要加上 open 關鍵字。
open class Room(val name:String){
fun description() = "Room: $name"
open fun load() = "Nothing much to see here..."
}

super 關鍵字

繼承父類別後,可以使用 super 參考父類別的屬性或函數。

範例:父類別加上 protected open val dangerLevel = 5

open class Room(val name:String){
protected open val dangerLevel = 5
fun description() = "Room: $name\\n" +
"Danger level: $dangerLevel"
open fun load() = "Nothing much to see here..."
}
  • 在子類別覆寫,並使用 super 參考父類別的 dangerLevel 值
class TownSquare: Room("Town Square"){
override val dangerLevel = super.dangerLevel - 3
override fun load() = "The villagers rally and cheer as your enter!"
}

子類別增加自訂屬性和函數

class TownSquare: Room("Town Square"){
override val dangerLevel = super.dangerLevel - 3
private var bellSound = "GWONG"
override fun load() = "The villagers rally and cheer as your enter!\\n${ringBell()}" private fun ringBell() = "The bell tower announces your arrival. $bellSound"
}

呼叫子類別的函數

fun main(){
val currentRoom: Room = TownSquare()
println(currentRoom.description())
println(currentRoom.load())
}
//The villagers rally and cheer as your enter!
//The bell tower announces your arrival. GWONG

多型 (Polymorphism)

在上面的範例中,currentRoom 宣告的型別是 Room,但是產生的實體為 TownSquare 類別。因為 TownSquare 是 Room 的子類別,所以這樣宣告是可以的。

同理,若有另外一個類別繼承 TownSquare,也可以給 currentRoom 使用。

這就是多型,多型的設計特性能夠簡化程式碼結構。透過多型,便可在一組類別裡複用個性化函數。

final 關鍵字

每一個類別的屬性及函數都能夠被複寫,若不想被複寫,可以使用 final 關鍵字修飾符。

class TownSquare: Room("Town Square"){
override val dangerLevel = super.dangerLevel - 3
private var bellSound = "GWONG"
final override fun load() = "The villagers rally and cheer as your enter!\\n${ringBell()}" private fun ringBell() = "The bell tower announces your arrival. $bellSound"
}
  • 現在任何 TownSquare 的子類別都只能複寫 description 函數,更動不了 load 函數。

類型檢測 (Type checking)

到目前為止,我們建立了一個父類別 Room,並由 TowerSquare 繼承它,繼承關係圖如下:

is 運算子

將 is 運算子左邊物件的類型和右邊的類型相比較,相同則回傳 true,反之回傳 false。

var room = Room("Foyer")
var townSquare = TownSquare()
room is Room //trueroom is TownSquare //falsetownSquare is TownSquare // true
townSquare is Room // true

Any 類

在 Kotlin 中,所有的類別都是繼承 Any 類,所以 TowerSquare 的繼承關係圖,應改為:

var room = Room("Foyer")
var townSquare = TownSquare()
room is Any //truetownSquare is Any // true

型別轉換

fun printIsSourceOfBlessings(any:Any){
val isSourceOfBlessings = if (any is Player){
any.isBlessed
}else{
(any as Room).name == "Fount of Blessing"
}
print("$any is a source of blessings: $isSourceOfBlessings")
}
  • 上面範例中,函數 printIsSourceOfBlessings 傳入引數的型別為 Any,也就是說任意的型別的值都可以傳進來。
  • if 運算式用 is 運算子判斷 any 是否為 Player 類,如果成立,表示 any 是 Player 類,所以就可以直接讓 any 使用 Player 的屬性,這稱為「智慧型別轉換」。反之,若不成立,表示 any 不是 Player 類,那是什麼類呢?編譯器不知道,所以不能夠參考其他類的屬性。利用 as 運算子將傳入的引數 any (型別為 Any) 轉換為 Room。

小結

  • 每一個類別在 Kotlin 中,預設都是不能被繼承的,必須要將類別加上關鍵字 open,才允許子類別繼承。這一點與 Java 有很大的不同 (在 Java 每一個類別預設都能夠被繼承,除非加上關鍵字 final。)
  • 要繼承父類別的函數,除了父類別需要加上 open 關鍵字外,還必須也在函數加上 open,才能夠開放給其他類別繼承。
  • 所有的類別都有隱性繼承 Any 類別。
  • 運算子 is 用來檢查型別;運算子 as 用來做類型轉換。

如果本篇文章有幫助到您,請 👏🏻鼓勵我。

謝謝 🙇🏻

參考

KotlinLang: Inheritance

Kotlin 權威2.0:Android 專家養成術 — 第十四章:繼承

--

--

Andy Lu
Andy Lu

Written by Andy Lu

Android/Flutter developer, Kotlin Expert, like to learn and share.

No responses yet