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 教程。