繼承
所有類都有一個共同的超類 Any
,對于沒有聲明超類型的類來說,Any
是其默認的超類:
// 隱式繼承自 Any。
class Example
Any
有三個方法:equals()
、hashCode()
和 toString()
。因此,所有類都定義了這些方法。
默認情況下,類是 final
的,即它們不能被繼承。若要使一個類可被繼承,需使用 open
關鍵字標記它:
// 該類可被繼承。
open class Base
要聲明一個顯式的超類型,在類頭中把類型放在冒號之后:
open class Base(p: Int)class Derived(p: Int) : Base(p)
如果派生類有主構造函數,基類必須根據其參數在該主構造函數中進行初始化。
如果派生類沒有主構造函數,那么每個次構造函數都必須使用 super
關鍵字來初始化基類型,或者委托給另一個能完成此操作的構造函數。注意,在這種情況下,不同的次構造函數可以調用基類型的不同構造函數:
class MyView : View {constructor(ctx: Context) : super(ctx)constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
方法重寫
對可重寫的成員和重寫操作使用顯式修飾符:
open class Shape {open fun draw() { /*...*/ }fun fill() { /*...*/ }
}class Circle() : Shape() {override fun draw() { /*...*/ }
}
對于 Circle.draw()
方法,override
修飾符是必需的。如果缺少該修飾符,編譯器將會報錯。如果一個函數沒有 open
修飾符,比如 Shape.fill()
,那么在子類中聲明具有相同簽名的方法是不允許的,無論是否使用 override
修飾符。對于 final
類(即沒有 open
修飾符的類)的成員,添加 open
修飾符是沒有效果的。
標記為 override
的成員本身是 open
的,因此它可以在子類中被再次重寫。如果你想禁止再次重寫,可以使用 final
修飾符:
open class Rectangle() : Shape() {final override fun draw() { /*...*/ }
}
屬性重寫
屬性的重寫機制和方法的重寫機制類似。在超類中聲明的屬性,若要在派生類中重新聲明,必須在前面加上 override
關鍵字,并且它們的類型必須兼容。每個已聲明的屬性可以被一個帶有初始值設定項的屬性或一個帶有 get
方法的屬性重寫:
open class Shape {open val vertexCount: Int = 0
}class Rectangle : Shape() {override val vertexCount = 4
}
你也可以用一個 var
屬性重寫一個 val
屬性,但反之則不行。這是允許的,因為 val
屬性本質上聲明了一個 get
方法,而將其重寫為 var
屬性時,還會在派生類中額外聲明一個 set
方法。
注意,你可以在主構造函數中把 override
關鍵字作為屬性聲明的一部分來使用:
interface Shape {val vertexCount: Int
}class Rectangle(override val vertexCount: Int = 4) : Shapeclass Polygon : Shape {override var vertexCount: Int = 0
}
派生類初始化順序
在創建派生類的新實例的過程中,基類的初始化是第一步(僅在對基類構造函數的參數求值之后進行),這意味著基類的初始化會在派生類的初始化邏輯執行之前完成。
open class Base(val name: String,) {init { println("Initializing a base class") }open val size: Int = name.length.also {println("Initializing size in the base class: $it")}
}class Derived(name: String, val lastName: String,): Base(name.replaceFirstChar { it.uppercase() }.also { println("Argument for the base class: $it") })
{init { println("Initializing a derived class") }override val size: Int = (super.size + lastName.length).also {println("Initializing size in the derived class: $it")}
}fun main() {// Argument for the base class: Aaa// Initializing a base class// Initializing size in the base class: 3// Initializing a derived class// Initializing size in the derived class: 6Derived("aaa", "bbb")
}
這意味著當基類構造函數執行時,派生類中聲明或重寫的屬性尚未初始化。在基類的初始化邏輯中使用這些屬性(無論是直接使用,還是通過另一個被重寫的開放成員的實現間接使用)可能會導致錯誤的行為或運行時故障。因此,在設計基類時,你應該避免在構造函數、屬性初始化器或初始化塊中使用開放成員。
調用超類實現
派生類中的代碼可以使用 super
關鍵字調用其超類的函數和屬性訪問器實現:
open class Rectangle {open fun draw() { println("Drawing a rectangle") }val borderColor: String get() = "black"
}class FilledRectangle : Rectangle() {override fun draw() {super.draw()println("Filling the rectangle")}val fillColor: String get() = super.borderColor
}
在內部類中,如果要訪問外部類的超類,可以使用帶有外部類名限定的 super
關鍵字,即 super@Outer
這種形式。下面通過具體的代碼示例來詳細解釋:
open class SuperClass {open fun doSomething() {println("SuperClass is doing something.")}
}class Outer : SuperClass() {override fun doSomething() {println("Outer class is doing something.")}inner class Inner {fun callSuperClassMethod() {super@Outer.doSomething() }}
}fun main() {// SuperClass is doing something.Outer().Inner().callSuperClassMethod()
}
open class SuperClass {open fun doSomething() {println("SuperClass is doing something.")}
}class Outer : SuperClass() {override fun doSomething() {println("Outer class is doing something.")}inner class Inner {fun callSuperClassMethod() {doSomething() }}
}fun main() {// Outer class is doing something.Outer().Inner().callSuperClassMethod()
}
重寫規則
實現繼承受以下規則約束:如果一個類從其直接超類繼承了同一成員的多個實現,那么它必須重寫該成員并提供自己的實現(也許可以使用其中一個繼承來的實現)。
為了指明從哪個超類型獲取繼承的實現,可以使用尖括號限定超類型名稱的 super
,例如 super<Base>
:
open class Rectangle {open fun draw() {}
}interface Polygon {// 接口成員默認是 open 的。fun draw() {}
}class Square() : Rectangle(), Polygon {// 編譯器要求重寫 draw()。override fun draw() {super<Rectangle>.draw() // 調用 Rectangle 的 draw() 方法。super<Polygon>.draw() // 調用 Polygon 的 draw() 方法。}
}
從 Rectangle
和 Polygon
繼承是沒問題的,但是它們都有各自的 draw()
實現,所以需要在 Square
類中重寫 draw()
并提供一個單獨的實現來消除歧義。