ZetCode

Kotlin override 关键字

最后修改于 2025 年 4 月 19 日

Kotlin 的 override 关键字通过方法和属性重写实现多态。它对于基于继承的设计至关重要。本教程将通过实际示例深入探讨 override 关键字。

基本定义

Kotlin 中的 override 关键字标记重写超类实现的成员。重写方法或属性时必须使用它。Kotlin 需要显式重写以防止意外重写。基类成员必须使用 open 标记以允许重写。

基本方法重写

override 的最简单用法是为继承的方法提供新的实现。基方法必须标记为 open

BasicOverride.kt
package com.zetcode

open class Animal {
    open fun makeSound() {
        println("Some generic animal sound")
    }
}

class Cat : Animal() {
    override fun makeSound() {
        println("Meow")
    }
}

fun main() {
    val animal: Animal = Cat()
    animal.makeSound() // Output: Meow
}

在这里,我们在 Cat 类中重写了 makeSound 方法。基类方法被标记为 open,允许重写。当我们对 Cat 实例调用 makeSound() 时,会执行重写后的版本。

属性重写

Kotlin 中也可以使用 override 关键字重写属性。与方法一样,基本属性也必须标记为 open

PropertyOverride.kt
package com.zetcode

open class Vehicle {
    open val wheels: Int = 4
}

class Bicycle : Vehicle() {
    override val wheels: Int = 2
}

fun main() {
    val vehicle: Vehicle = Bicycle()
    println(vehicle.wheels) // Output: 2
}

此示例演示了属性重写。Bicycle 类重写了 Vehicle 的 wheels 属性。重写将值从 4 更改为 2。属性重写遵循与方法重写类似的规则。

调用超类实现

super 关键字允许从重写中访问超类实现。当您想要扩展而不是替换行为时,这很有用。

SuperCall.kt
package com.zetcode

open class Logger {
    open fun log(message: String) {
        println("Log: $message")
    }
}

class TimestampLogger : Logger() {
    override fun log(message: String) {
        super.log("${java.time.LocalTime.now()} $message")
    }
}

fun main() {
    val logger: Logger = TimestampLogger()
    logger.log("System started") // Output: Log: [current time] System started
}

在这里,我们重写了 log 方法,但使用 super 调用原始实现。我们通过添加时间戳前缀来增强它。这种模式在扩展功能而不是完全替换它时很常见。

重写抽象成员

抽象成员必须在具体子类中重写。仍然需要 override 关键字,但基成员不需要 open

AbstractOverride.kt
package com.zetcode

abstract class Shape {
    abstract fun area(): Double
}

class Circle(val radius: Double) : Shape() {
    override fun area(): Double {
        return Math.PI * radius * radius
    }
}

fun main() {
    val shape: Shape = Circle(5.0)
    println(shape.area()) // Output: 78.53981633974483
}

Circle 类必须重写 Shape 的抽象 area() 方法。与常规重写不同,抽象成员不需要 open,因为它们隐式地对实现开放。重写提供了具体的实现。

多接口实现

当实现具有冲突成员的多个接口时,您必须显式地重写它们。override 关键字解决了歧义问题。

MultipleInterfaces.kt
package com.zetcode

interface A {
    fun foo() { println("A") }
}

interface B {
    fun foo() { println("B") }
}

class C : A, B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
        println("C")
    }
}

fun main() {
    val c = C()
    c.foo() // Output: A B C
}

类 C 实现了同时具有冲突 foo() 方法的接口 A 和 B。我们重写 foo() 并使用 super 和尖括号来指定要调用哪个接口的实现。这解决了歧义问题,同时允许我们组合行为。

继承的重写规则

Kotlin 在继承层次结构中具有特定的重写规则。派生类可以重写 open 成员,并且它们自己可以对进一步的重写开放。

InheritanceRules.kt
package com.zetcode

open class Base {
    open fun foo() { println("Base") }
}

open class Derived : Base() {
    final override fun foo() { println("Derived") }
}

class FinalDerived : Derived() {
    // Cannot override foo() here because it's final in Derived
}

fun main() {
    val obj: Base = Derived()
    obj.foo() // Output: Derived
}

Derived 类重写了 Base 中的 foo() 并将其标记为 final 以防止进一步的重写。FinalDerived 不能重写 foo()。这展示了如何在类层次结构中控制重写链。

使用不同类型重写属性

重写属性可以具有比其超类对应项更具体的类型。这在方法重写中被称为协变返回类型。

CovariantOverride.kt
package com.zetcode

open class Animal {
    open val food: Any = "Generic food"
}

class Cat : Animal() {
    override val food: String = "Fish"
}

fun main() {
    val animal: Animal = Cat()
    println(animal.food) // Output: Fish
}

在这里,我们使用更具体的类型(String 而不是 Any)重写 food 属性。这是允许的,因为 String 是 Any 的子类型。重写提供了更具体的类型信息,同时保持类型安全。

重写的最佳实践

来源

Kotlin 继承文档

本教程深入探讨了 Kotlin 的 override 关键字,展示了在各种情况下的方法和属性重写。我们探讨了继承、接口、抽象成员和协变类型。正确使用重写可以实现多态,同时保持类型安全。

作者

我的名字是 Jan Bodnar,我是一位充满激情的程序员,拥有多年的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我撰写了 1400 多篇文章和 8 本电子书。 我有超过八年的编程教学经验。

列出 所有 Kotlin 教程