Menu
CoddyTech

覆盖成员

本课属于 Coddy Kotlin 学习旅程的“面向对象编程”单元。第 18 课,共 57 课。

子类可以用自己的版本替换继承的方法。超类将方法标记为 open,子类则使用 override 再次声明它:

open class Animal(val name: String) {
    open fun sound() = "..."
}
class Dog(name: String) : Animal(name) {
    override fun sound() = "Woof"
}
class Cat(name: String) : Animal(name) {
    override fun sound() = "Meow"
}

在 main 中:

println(Dog("Rex").sound())
println(Cat("Tom").sound())
println(Animal("Generic").sound())

输出:

Woof
Meow
...

实际运行的版本由对象的实际类决定,即使变量的类型是超类也一样。超类中的代码调用开放方法时,也会使用子类的版本:

open class Animal(val name: String) {
    open fun sound() = "..."
    fun speak() = "$name says ${sound()}"
}
class Dog(name: String) : Animal(name) {
    override fun sound() = "Woof"
}

在 main 中:

val pets: List<Animal> = listOf(Dog("Rex"), Animal("Generic"))
for (p in pets) println(p.speak())

输出:

Rex says Woof
Generic says ...

属性也可以用同样的方式重写:在超类中使用 open val,在子类中使用 override val,可以将其声明为普通属性、构造函数属性或计算属性 getter:

open class Shape {
    open val name = "shape"
    open val corners = 0
}
class Triangle : Shape() {
    override val name = "triangle"
    override val corners = 3
}
class Polygon(override val corners: Int) : Shape() {
    override val name: String
        get() = "$corners-gon"
}

在 main 中:

for (s in listOf(Shape(), Triangle(), Polygon(6))) println("${s.name}: ${s.corners}")

输出:

shape: 0
triangle: 3
6-gon: 6

标记为 override 的成员会再次开放,因此子类的子类可以再次重写它;final override 会阻止这种情况。没有 open 的成员完全不能被重写,子类也不能声明具有相同签名的成员:

open class Animal {
    open fun sound() = "..."
    fun sleep() = "zzz"
}
open class Dog : Animal() {
    final override fun sound() = "Woof"
}
class Puppy : Dog() {
    // override fun sound() = "Yip"   // 错误:'Dog' 中的 'sound' 是 final,不能重写
    // fun sleep() = "nap"            // 错误:'sleep' 隐藏了父类的一个成员
}
challenge icon

挑战

简单

Animal 有 legs()(4)、sound()(...)和 describe(),后者返回由 legs() 和 sound() 生成的 Rex has 4 legs and says Woof。让 legs() 和 sound() 可以被重写,并且只重写这两个方法来完成子类:Dog 发出 Woof 声,Bird 有 2 条腿并发出 Tweet 声,Snake 有 0 条腿并发出 Hiss 声。describe() 在 Animal 中保持原样。

提供的代码会读取 dog,Rex、bird,Pip、snake,Kaa 或其他任意类型的行(对应一个普通的 Animal),将这些动物存储在一个 List<Animal> 中,并为每个动物打印 describe() 的结果。

你的代码应写在 Animal.kt、Dog.kt、Bird.kt 和 Snake.kt 中。Main.kt 包含提供的输入/输出代码,不能编辑。

亲手试试

fun main() {
    // 已提供的输入输出代码:保持原样
    val input = generateSequence(::readLine).toList()
    val animals = mutableListOf<Animal>()
    for (line in input) {
        val (kind, name) = line.split(",")
        animals.add(when (kind) {
            "dog" -> Dog(name)
            "bird" -> Bird(name)
            "snake" -> Snake(name)
            else -> Animal(name)
        })
    }
    for (a in animals) println(a.describe())
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Kotlin 在线编辑器