Kotlin 繼承 (Inheritance)
繼承是物件導向程式設計 (OOP) 的核心概念之一,它允許我們基於一個已存在的類別 (父類別) 來建立新的類別 (子類別),從而重用程式碼並建立類別之間的階層關係。
在 Kotlin 中,所有的類別都有一個共同的頂層父類別 Any (類似 Java 的 Object)。
開放繼承 (Open Classes)
Kotlin 與 Java 最大的不同點在於:Kotlin 的類別預設是 final 的,也就是說,預設情況下禁止被繼承。
如果你希望一個類別可以被繼承,必須明確地加上 open 關鍵字。
// 父類別 (Superclass)
// 加上 open 關鍵字,允許被繼承
open class Animal {
// 方法也是預設 final,若允許子類別覆寫 (override),也要加 open
open fun makeSound() {
println("Some generic animal sound")
}
}
// 子類別 (Subclass)
// 使用冒號 : 來繼承父類別,並且必須呼叫父類別的建構函數 ()
class Dog : Animal() {
// 使用 override 關鍵字來覆寫父類別的方法
override fun makeSound() {
println("Bark!")
}
}
fun main() {
val dog = Dog()
dog.makeSound() // 輸出: Bark!
}
覆寫屬性 (Overriding Properties)
除了方法,屬性 (Properties) 也可以被覆寫。規則與方法類似:
- 父類別屬性必須標記為
open。 - 子類別使用
override關鍵字進行覆寫。
open class Shape {
open val vertexCount: Int = 0
}
class Rectangle : Shape() {
// 覆寫屬性
override val vertexCount: Int = 4
}
你可以用
var 屬性覆寫 val 屬性,但反過來不行。
因為 val 只有 getter,而 var 有 getter 和 setter。用 var 覆寫 val 相當於在子類別中由原本的「唯讀」擴充為「可讀寫」。禁止再次覆寫 (Final Override)
當你覆寫一個 open 的方法或屬性後,在子類別中該成員預設仍然是 open 的 (可以被更下層的子類別繼續覆寫)。
如果你希望到此為止,禁止後續的子類別再次覆寫,可以在 override 前加上 final。
open class Base {
open fun v() {}
}
open class Derived : Base() {
// 這裡覆寫了 v(),並標記為 final,禁止後續子類別再次覆寫
final override fun v() {}
}
初始化順序 (Initialization Order)
當建立一個子類別的實體時,初始化的執行順序非常重要:
- 父類別 (Base Class) 的初始化區塊 (init block) 和屬性初始化會先執行。
- 子類別 (Derived Class) 的初始化區塊和屬性初始化才會執行。
這意味著在父類別的建構過程中,子類別宣告的屬性可能還沒被初始化。
open class Base(val name: String) {
init {
println("Initializing Base")
}
}
class Derived(name: String) : Base(name) {
init {
println("Initializing Derived")
}
}
fun main() {
val d = Derived("Test")
}
// 輸出順序:
// Initializing Base
// Initializing Derived
因此,強烈建議不要在父類別的建構函數或 init 區塊中,呼叫任何
open 的成員函式。
因為這時子類別還沒初始化完成,如果該 open 函式依賴了子類別的屬性,可能會導致非預期的錯誤 (例如存取到還未賦值的屬性)。呼叫父類別實作 (Calling the Superclass)
在子類別中,可以使用 super 關鍵字來呼叫父類別的函式或屬性存取器。
open class Base {
open fun draw() {
println("Drawing Base")
}
}
class Derived : Base() {
override fun draw() {
super.draw() // 呼叫父類別原本的 draw()
println("Drawing Derived")
}
}
內部類別存取外部父類別 (Inner Class)
如果你在一個內部類別 (Inner Class) 中想要存取外部類別的父類別,需要使用 super@Outer 的語法:
class FilledRectangle : Rectangle() {
override fun draw() {
val filler = Filler()
filler.drawAndFill()
}
// 內部類別
inner class Filler {
fun fill() { println("Filling") }
fun drawAndFill() {
super@FilledRectangle.draw() // 呼叫外部類別 Rectangle 的 draw()
fill()
}
}
}
抽象類別 (Abstract Classes)
如果一個類別主要目的是作為其他類別的基礎,且某些成員不需要在當下實作,可以宣告為 abstract。
- 抽象類別不需要加
open(預設就是 open)。 - 抽象成員 (方法或屬性)不需要加
open,且沒有實作本體。 - 抽象類別不能被實例化 (不能
new出來)。
abstract class Polygon {
// 抽象方法,沒有大括號 {} 實作
abstract fun draw()
}
class Rectangle : Polygon() {
// 子類別「必須」實作所有的抽象成員,除非子類別也是 abstract
override fun draw() {
println("Drawing a rectangle")
}
}