Kotlin override 关键字
最后修改于 2025 年 4 月 19 日
Kotlin 的 override 关键字通过方法和属性重写实现多态。它对于基于继承的设计至关重要。本教程将通过实际示例深入探讨 override 关键字。
基本定义
Kotlin 中的 override 关键字标记重写超类实现的成员。重写方法或属性时必须使用它。Kotlin 需要显式重写以防止意外重写。基类成员必须使用 open 标记以允许重写。
基本方法重写
override 的最简单用法是为继承的方法提供新的实现。基方法必须标记为 open。
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。
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 关键字允许从重写中访问超类实现。当您想要扩展而不是替换行为时,这很有用。
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。
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 关键字解决了歧义问题。
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 成员,并且它们自己可以对进一步的重写开放。
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()。这展示了如何在类层次结构中控制重写链。
使用不同类型重写属性
重写属性可以具有比其超类对应项更具体的类型。这在方法重写中被称为协变返回类型。
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 的子类型。重写提供了更具体的类型信息,同时保持类型安全。
重写的最佳实践
- 谨慎使用 open: 仅当预期进行有意重写时,才将成员标记为 open。
- 记录重写: 清楚地记录重写实现必须遵循的契约。
- 考虑 final: 当不需要进一步重写时,将重写标记为 final。
- 维护 LSP: 在重写中遵循 Liskov 替换原则。
- 明智地使用 super: 在扩展行为时适当地调用 super 实现。
来源
本教程深入探讨了 Kotlin 的 override 关键字,展示了在各种情况下的方法和属性重写。我们探讨了继承、接口、抽象成员和协变类型。正确使用重写可以实现多态,同时保持类型安全。
作者
列出 所有 Kotlin 教程。