跳到内容

第 1 卷 / 第 8 章 / 第 3 课

8.3 方法重写、@Override 与 super ​

前置知识:继承、方法签名和构造链

预计时间:30 分钟

完成标志:能判断重写是否合法,并能区分重写、重载、隐藏和不可重写成员

你本想重写 send(),手一抖写成 snd()。没有额外检查时,编译器会把它当成一个新方法,调用方仍执行父类版本。

1. @Override 把意图交给编译器检查 ​

本页沿用继承一讲的 MessageSender,但换用下面这个单参数 EmailSender;把不同版本分开放。send() 仍只打印演示信息。

java
class EmailSender extends MessageSender {
    EmailSender(String message) {
        super(message);
    }

    @Override
    public void send() {
        super.send();
        System.out.println("demo email channel");
    }
}

@Override 不负责动态分派;它验证该声明确实覆盖了父类或接口中的可重写方法。拼错名称、参数类型或参数数量都会在编译期暴露。实现接口方法时也应使用它。

2. 合法重写不只要求“同名同参数” ​

Java 17 中需要同时满足这些核心约束:

  • 方法签名具有 override-equivalent 关系,初学时可理解为名称和形参类型一致;
  • 返回类型相同,或是允许的协变引用返回类型;
  • 子类不能缩小访问权限;
  • 子类不能声明比父方法更宽的受检异常;
  • 父方法必须可被继承且不是 final/static/private。
java
class Factory {
    protected Number create() throws java.io.IOException {
        return 0;
    }
}

class IntegerFactory extends Factory {
    @Override
    public Integer create() {
        return 1;
    }
}

public 比 protected 更开放,Integer 是 Number 的子类型,而且子类省略了受检异常,因此合法。

泛型擦除还会引入桥接方法等细节,等学完泛型再深入;不要用“返回类型完全一样”作为永远正确的口诀。

3. 哪些成员不会发生实例方法重写 ​

成员实际规则
final 实例方法继承但不能重写
private 方法不被子类继承,子类同名方法是新声明
static 方法按类隐藏,不做实例动态分派
字段可发生同名隐藏,访问取决于表达式静态类型
构造方法不继承,也不重写
java
class Parent {
    static String kind() { return "parent"; }
    String label = "parent";
}

class Child extends Parent {
    static String kind() { return "child"; }
    String label = "child";
}

Parent value = new Child();
System.out.println(Parent.kind()); // parent
System.out.println(value.label);  // parent

这正是为什么字段通常 private,static 方法用类名调用。把它们当成实例多态会产生难读代码。

4. super 从直接父类查找实现 ​

java
@Override
public void send() {
    super.send();
    System.out.println("子类附加行为");
}

super.send() 是编译期确定的父类调用,不会再次动态分派回当前覆盖方法。是否调用它取决于父类契约,不是所有重写都必须“先 super”。若父方法规定必要不变量,更稳妥的设计可能是把流程设为 final,只开放受控钩子。

Java 17 中,构造体里的 super(...) 选择父类构造,必须是第一条语句;它和 super.send() 是两种不同上下文。

5. 重写与重载在不同阶段决策 ​

java
class Dispatcher {
    void deliver(MessageSender sender) {
        System.out.println("sender overload");
    }

    void deliver(EmailSender sender) {
        System.out.println("email overload");
    }
}

MessageSender value = new EmailSender("消息");
new Dispatcher().deliver(value); // sender overload

重载在编译期根据方法名、实参数量和编译期类型选择。运行时对象是 EmailSender,不会让调用改选另一个重载。

重写则是在已经选定实例方法签名后,由 JVM 根据接收者实际类选择最具体的覆盖实现。先分清这两个阶段,才能预测“重载 + 重写”混合代码。

常见编译失败 ​

java
class BrokenSender extends MessageSender {
    BrokenSender(String message) { super(message); }

    // @Override
    // protected void send() {} // 不能缩小 public 访问权限
}
java
class TypoSender extends MessageSender {
    TypoSender(String message) { super(message); }

    // @Override
    // public void snd() {} // 没有可覆盖的方法
}

故意失败的教学片段应单独编译确认诊断,不要只靠注释猜测。

专业速查 ​

术语决策阶段关键依据
重载编译期方法名、形参数量与编译期类型
重写运行时分派已选签名 + 接收者实际类
static 隐藏编译期限定类或表达式静态类型
字段隐藏编译期表达式静态类型

离开这一页前 ​

为一次重写逐项核对签名、协变返回、访问权限和受检异常,并让拼错方法名、缩小权限两份最小源码由 javac 拒绝。再用 Parent value = new Child() 分别预测字段、static 方法和实例方法,最后说明重载先选签名、重写再选实现。

→ 下一步:抽象类、接口与 Object

规范入口:JLS §8.4.8.1:实例方法重写。

Built with VitePress | Software Systems Atlas