8.3 方法重写、@Override 与 super
前置知识:继承、方法签名和构造链
预计时间:30 分钟
完成标志:能判断重写是否合法,并能区分重写、重载、隐藏和不可重写成员
你本想重写 send(),手一抖写成 snd()。没有额外检查时,编译器会把它当成一个新方法,调用方仍执行父类版本。
1. @Override 把意图交给编译器检查
本页沿用继承一讲的 MessageSender,但换用下面这个单参数 EmailSender;把不同版本分开放。send() 仍只打印演示信息。
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。
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 方法 | 按类隐藏,不做实例动态分派 |
| 字段 | 可发生同名隐藏,访问取决于表达式静态类型 |
| 构造方法 | 不继承,也不重写 |
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 从直接父类查找实现
@Override
public void send() {
super.send();
System.out.println("子类附加行为");
}super.send() 是编译期确定的父类调用,不会再次动态分派回当前覆盖方法。是否调用它取决于父类契约,不是所有重写都必须“先 super”。若父方法规定必要不变量,更稳妥的设计可能是把流程设为 final,只开放受控钩子。
Java 17 中,构造体里的 super(...) 选择父类构造,必须是第一条语句;它和 super.send() 是两种不同上下文。
5. 重写与重载在不同阶段决策
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 根据接收者实际类选择最具体的覆盖实现。先分清这两个阶段,才能预测“重载 + 重写”混合代码。
常见编译失败
class BrokenSender extends MessageSender {
BrokenSender(String message) { super(message); }
// @Override
// protected void send() {} // 不能缩小 public 访问权限
}class TypoSender extends MessageSender {
TypoSender(String message) { super(message); }
// @Override
// public void snd() {} // 没有可覆盖的方法
}故意失败的教学片段应单独编译确认诊断,不要只靠注释猜测。
专业速查
| 术语 | 决策阶段 | 关键依据 |
|---|---|---|
| 重载 | 编译期 | 方法名、形参数量与编译期类型 |
| 重写 | 运行时分派 | 已选签名 + 接收者实际类 |
| static 隐藏 | 编译期 | 限定类或表达式静态类型 |
| 字段隐藏 | 编译期 | 表达式静态类型 |
离开这一页前
为一次重写逐项核对签名、协变返回、访问权限和受检异常,并让拼错方法名、缩小权限两份最小源码由 javac 拒绝。再用 Parent value = new Child() 分别预测字段、static 方法和实例方法,最后说明重载先选签名、重写再选实现。
规范入口:JLS §8.4.8.1:实例方法重写。