首页 / Swift 编程语言教程 / 重写 override

Swift 编程语言教程

重写 override

本教程共 93 篇 · 第 62 篇 · 更新于 2026-08-08 · 约 8 分钟阅读

SwiftSwift 编程语言教程重写override属性重写方法重写super观察器

本节目标:学完你能重写父类的方法与属性,给继承来的属性加观察器,并清楚何时该用 super 调用原版。

第 61 章说过,子类可以提供自己的实现来替换从父类继承来的成员,这叫重写(override)。这一章把重写的各种具体姿势拆开:方法怎么重写、属性怎么重写、观察器怎么加,以及哪些红线不能踩。记住一条总纲:凡是要替换继承来的成员,前面都得老老实实写 override

1-1 重写方法

重写方法最直接:子类写个同名同参数的方法,前面加 override,就能用自己的版本替换父类的。

class Vehicle {
    var currentSpeed = 0.0
    var description: String {
        return "以 \(currentSpeed) 英里每小时行驶"
    }
    func makeNoise() {
        // 基类什么都不做
    }
}
class Train: Vehicle {
    override func makeNoise() {
        print("呜——")
    }
}

创建 Train 实例调用 makeNoise(),跑的是子类版本,打印”呜——“。实例方法和类型方法都能重写。类型方法若父类用 class 声明,子类重写时也用 class;若父类用 static,则 static 本身不可重写,除非父类标的是 class

1-2 重写属性的 getter 和 setter

属性的重写比方法灵活。你可以重写任何继承来的属性——不管它在父类里是存储属性还是计算属性——提供自己的 getter(必要时加 setter)。子类压根不需要知道父类的属性底层是存出来的还是算出来的,它只认”名字和类型”。

class Car: Vehicle {
    var gear = 1
    override var description: String {
        return super.description + ",挡位 \(gear)"
    }
}

Car 重写了从 Vehicle 继承来的 description(它是个只读计算属性),在父类描述后拼上挡位。注意重写属性时必须同时写明属性的名字和类型,编译器要靠这俩去父类核对匹配。

有两条铁律。第一,你可以把继承来的只读属性”升级”成可读写属性(提供 getter + setter),但反过来不行——不能把可读写属性降级成只读,因为那会剥夺父类已有的赋值能力。第二,如果你在重写里提供了 setter,就也必须提供 getter;若你不想改值,getter 里直接 return super.someProperty 把父类的值透传回去即可,这很常见。

1-3 重写属性加观察器

除了重写 getter/setter,你还能通过属性重写给继承来的属性挂上 willSet/didSet 观察器,从而在它每次变化时收到通知——无论那个属性在父类里原本怎么实现。

class AutomaticCar: Car {
    override var currentSpeed: Double {
        didSet {
            gear = Int(currentSpeed / 10.0) + 1
        }
    }
}

AutomaticCar 重写了继承来的 currentSpeed,在 didSet 里根据新速度自动选挡。速度一变,挡位就跟着调。比如设成 35.0,挡位自动变成 4。这把”属性变化”和”联动逻辑”优雅地绑在了一起。

这里有两个禁区要避开。其一,你不能给继承来的常量存储属性、或只读计算属性加观察器——它们的值根本没法被你重新赋,加了也没触发时机。其二,你不能对同一个属性既提供重写的 setter、又提供重写的观察器。如果你已经在 setter 里改值,直接在 setter 内部写观察逻辑就行,不必再叠观察器。

1-4 用 super 衔接父类逻辑

重写时,super 是你衔接父类的桥梁。是否调用 super,取决于你的意图:

  • 想在父类结果上”加工”,就调用 super.xxx 拿到原版再处理(如 Cardescription)。
  • 想”完全替换”父类行为,就不调用 super,自己从头写。
class Animal {
    func speak() { print("...") }
}
class Dog: Animal {
    override func speak() {
        super.speak()      // 先叫一声父类的基础音
        print("汪汪")        // 再补上自己的
    }
}

这种”先父后子”的写法在初始化、视图生命周期等场景尤其多。但要小心:不是所有重写都该调 super,覆盖式重写调了反而可能重复执行,得看语义。

1-5 重写和下标

方法和属性之外,下标(subscript)也能重写。语法一样:子类写下标时加 override,用 super[索引] 访问父类版本。下标本是给类型提供”用方括号取值”的能力(第 54 章讲过),继承后同样可以特化。用法和属性重写对称,这里不再赘述。

1-6 和 final 的配合

第 61 章提过,final 能锁死成员。被 final 修饰的方法、属性、下标,子类写 override 会直接编译报错。所以”哪些该开放重写、哪些该用 final 封死”是一道设计题:对外开放的扩展点用普通声明,确定不变的用 final,整类不可继承就 final class。把这道关卡想清楚,你的类层次既灵活又安全。

1-7 小结

重写的本质是用子类版本替换继承来的成员,override 关键字强制且受编译器核对。方法重写最直白;属性重写可换 getter/setter 或加观察器,但不可把可读写降级为只读、也不可同时既写 setter 又写观察器;super 用来衔接父类逻辑。掌握这些边界,继承树才能既好扩展又不出乱子。

1-8 重写能再被重写吗

子类重写了父类方法后,更下层的子类还能再重写它——重写是可以叠加的。比如 Vehicle.makeNoise()Car 重写,Car 的子类还能再重写一次,形成逐层特化。每层都能用 super 调上一层的版本,串成一条调用链。这既是灵活之处,也是风险之处:链太长时,一次调用到底跑了哪些版本会变得难追踪。所以重写要克制,只在确有差异时覆盖,别为了”顺手加一句”就层层重写。

1-9 重写时的访问控制

重写还受访问控制约束。你能把父类里访问级别更宽松的成员重写成更严格吗?不行——重写不能收紧对外可见性,否则外部通过这个父类引用却看不到该成员,会破坏”子类可当父类用”的承诺。Swift 在编译期核对这点。具体的 open/public/internal 规则放到第 73 章再系统讲,这里你只需记住:重写是”对外能力不能缩水”的操作。反之,你可以把更严格的成员重写成更宽松(比如父类 internal,子类 open),这符合”子类更开放”的方向。掌握这个松紧原则,你在设计可被继承的公开 API 时才不会踩坑。

1-10 重写与重载别混

最后厘清一个长期混淆:重写(override)和重载(overload)不是一回事。重载是在同一个类型里写多个同名但参数不同的函数,编译器在编译期按参数选版本;重写是子类替换父类已有的同名同签名成员,运行期按真实类型派发。多态那章讲的动态行为来自重写,不是重载。把这两个词分清楚,你看报错和设计方案时都不会再迷糊。简单记:override 跨继承、运行期;overload 同作用域、编译期。