Java方法重写技术问询:父类Glyph与子类RoundGlyph代码解析
Java方法重写机制解析——结合构造器调用重写方法的典型场景
这是个非常经典的Java方法重写+对象初始化流程的问题,刚好能帮你把重写机制和构造阶段的坑点串起来讲清楚。
首先先明确:方法重写(Override)是子类对父类中非private、非static、非final的方法进行重新实现,它是Java实现运行时多态的核心。重写必须遵循“两同两小一大”规则:
- 两同:方法名、参数列表完全相同
- 两小:子类方法的返回值类型≤父类(比如父类返回
Number,子类可以返回Integer) - 一大:子类方法的访问权限≥父类(比如父类是
protected,子类可以是public)
先看你给出的代码示例
父类Glyph:
public class Glyph { void draw() { System.out.println("Glyph.draw()"); } Glyph() { System.out.println("Glyph() before draw()"); draw(); System.out.println("Glyph() after draw()"); } }
子类RoundGlyph:
class RoundGlyph extends Glyph { int radius = 1; RoundGlyph(int r) { super(); radius = r; System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius); } @Override // 建议加上这个注解,编译器帮你检查是否符合重写规则 void draw() { System.out.println("RoundGlyph.draw(), radius = " + radius); } }
测试代码与执行结果
假设我们用这段代码测试:
public class Test { public static void main(String[] args) { new RoundGlyph(5); } }
执行后会输出:
Glyph() before draw() RoundGlyph.draw(), radius = 0 Glyph() after draw() RoundGlyph.RoundGlyph(), radius = 5
是不是有点意外?为什么draw()里的radius是0而不是1?咱们一步步拆解执行流程:
- 调用子类构造器:当
new RoundGlyph(5)执行时,首先进入子类的构造器,第一行显式调用super()(如果没写,编译器会自动加),也就是父类Glyph的构造器。 - 父类构造器执行:
- 先打印
Glyph() before draw() - 然后调用
draw()方法——这里是关键!因为当前正在创建的是RoundGlyph实例,JVM会根据实际对象类型调用子类重写后的draw()方法(这就是运行时多态的体现),而不是父类的draw()。
- 先打印
- 子类重写的draw()执行:此时子类的成员变量
radius还没完成初始化!Java对象初始化的顺序是:父类构造器执行 → 子类成员变量赋默认值(比如int是0)→ 子类构造器代码执行。所以此时radius还是默认值0,因此打印出RoundGlyph.draw(), radius = 0。 - 父类构造器收尾:打印
Glyph() after draw(),父类构造完成。 - 回到子类构造器:
- 执行
radius = r;,把radius赋值为传入的5 - 打印
RoundGlyph.RoundGlyph(), radius = 5
- 执行
关键知识点总结
- 运行时多态的本质:JVM在调用方法时,是根据对象的实际类型(而不是引用类型)来决定调用哪个版本的方法,这也是重写能生效的核心。
- 构造器调用重写方法的风险:父类构造器执行时,子类的成员变量还处于默认值状态(子类的初始化逻辑还没跑),此时调用子类重写的方法,很可能访问到未初始化的变量,导致不符合预期的结果。
- 避坑建议:尽量不要在构造器中调用可被重写的方法(即非private、非final的方法);如果必须调用,确保该方法不依赖子类的成员变量,或者把方法标记为
private/final,避免被子类重写。
内容的提问来源于stack exchange,提问作者Andrew
相关产品推荐
相关产品推荐

