Edit Page

继承

最终更新: 2024/03/21

Kotlin 中所有的类都有一个共同的超类 Any, 如果类声明时没有指定超类, 则默认为 Any:

class Example // 隐含地继承自 Any

Any 拥有三个函数: equals(), hashCode()toString(). 因此, Kotlin 的所有类都拥有这些函数.

默认情况下, Kotlin 的类是 final 的 - 不能再被继承. 如果要允许一个类被继承, 需要使用 open 关键字标记这个类:

open class Base // 这个类现在是 open 的, 可以被继承

要明确声明类的超类, 要在类的头部添加一个冒号, 冒号之后指定超类:

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)
}

方法的覆盖

Kotlin 要求使用明确的修饰符来标识允许被子类覆盖的成员, 也要求使用明确的修饰符来标识对超类成员的覆盖:

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 属性覆盖一个 var 属性. 允许这种覆盖的原因是, val 属性本质上只是定义了一个 get 方法, 使用 var 属性来覆盖它, 只是向后代类中添加了一个 set 方法.

注意, 你可以在主构造器的属性声明中使用 override 关键字:

interface Shape {
    val vertexCount: Int
}

class Rectangle(override val vertexCount: Int = 4) : Shape // 长方形总是拥有 4 个顶点

class Polygon : Shape {
    override var vertexCount: Int = 0  // 多边形的顶点数目不定, 可以变更为任何数字
}

子类的初始化顺序

子类新实例构造的过程中, 首先完成的第一步是要初始化基类 (顺序上仅次于计算传递给基类构造器的参数值), 因此要在子类的初始化逻辑之前执行.

//sampleStart
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") }
}
//sampleEnd

fun main() {
    println("Constructing the derived class(\"hello\", \"world\")")
    Derived("hello", "world")
}

也就是说, 基类构造器执行时, 在子类中定义或覆盖的属性还没有被初始化. 如果在基类初始化逻辑中使用到这些属性 (无论是直接使用, 还是通过另一个被覆盖的 open 成员间接使用), 可能会导致不正确的行为, 甚至导致运行时错误. 因此, 设计基类时, 在构造器, 属性初始化器, 以及 init 代码段中, 你应该避免使用 open 成员.

调用超类中的实现

后代类中的代码, 可以使用 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
}

在内部类(inner class)的代码中, 可以使用 super 关键字加上外部类名称限定符: super@Outer 来访问外部类(outer class)的超类:

open class Rectangle {
    open fun draw() { println("Drawing a rectangle") }
    val borderColor: String get() = "black"
}

//sampleStart
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()
            println("Drawn a filled rectangle with color ${super@FilledRectangle.borderColor}") // 使用 Rectangle 的 borderColor 属性的 get() 函数
        }
    }
}
//sampleEnd

fun main() {
    val fr = FilledRectangle()
        fr.draw()
}

覆盖的规则

在 Kotlin 中, 类继承中的方法实现问题, 遵守以下规则: 如果一个类从它的直接超类中继承了同一个成员的多个实现, 那么这个子类必须覆盖这个成员, 并提供一个自己的实现(可以使用继承得到的多个实现中的某一个). 为了表示使用的方法是从哪个超类继承得到的, 可以使用 super 关键字, 将超类名称放在尖括号类, 比如, super<Base>:

open class Rectangle {
    open fun draw() { /* ... */ }
}

interface Polygon {
    fun draw() { /* ... */ } // 接口的成员默认是 'open' 的
}

class Square() : Rectangle(), Polygon {
    // 编译器要求 draw() 方法必须覆盖:
    override fun draw() {
        super<Rectangle>.draw() // 调用 Rectangle.draw()
        super<Polygon>.draw() // 调用 Polygon.draw()
    }
}

同时继承 RectanglePolygon 是合法的, 但他们都实现了函数 draw() 的继承就发生了问题, 因此你需要在 Square 类中覆盖函数 draw(), 并提供单独的实现, 这样才能消除歧义.