
本文深入解析 Java 单继承中父类构造器调用子类重写方法时为何访问到未初始化的字段,揭示 Animal's name is: null 的根本原因及安全编码实践。
本文深入解析 java 单继承中父类构造器调用子类重写方法时为何访问到未初始化的字段,揭示 `animal's name is: null` 的根本原因及安全编码实践。
在 Java 的单继承体系中,对象创建过程严格遵循“先父后子、先静态后实例”的初始化顺序。当执行 new Dog() 时,JVM 首先隐式调用 super()(即 Animal() 构造器),此时 Dog 类的实例字段尚未初始化——String name = "Dog"; 这一行代码会在 Animal 构造器执行完毕、即将进入 Dog() 构造器体之前才执行。
关键在于:Java 中所有非 private、非 static、非 final 的实例方法都具有动态绑定(运行时多态)特性。尽管 Animal() 构造器中调用了 getName(),但当前正在构建的对象是 Dog 实例,因此 JVM 会查找到 Dog.getName()(重写版本),而非 Animal.getName()。
然而,此时 Dog.name 字段仍为默认值 null(字符串引用类型的默认初始值),因为字段初始化语句 name = "Dog"; 尚未执行。于是 Dog.getName() 返回 null,导致输出 Animal's name is: null。
以下是该流程的清晰时序分解:
立即学习“Java免费学习笔记(深入)”;
- new Dog() 触发;
- 隐式插入 super() → 进入 Animal() 构造器;
- Animal() 中执行 System.out.println("Animal's name is: " + getName());
- 动态绑定决定调用 Dog.getName();
- Dog.name 尚未初始化 → 值为 null → getName() 返回 null;
- Animal() 执行完毕;
- 初始化 Dog 实例字段:name = "Dog";
- 执行 Dog() 构造器体:System.out.println("Dog's name is: " + getName()) → 此时 name 已为 "Dog",输出正确。
✅ 正确实践建议:
- 避免在构造器中调用可被重写的方法(Effective Java 第17条),这是危险的设计模式;
- 若需在父类中访问自身字段,应将 getName() 声明为 final 或 private,阻止子类重写;
- 如必须依赖子类状态,可考虑使用工厂方法或延迟初始化(Lazy Initialization)。
class Animal {
protected final String name = "Animal"; // 使用 final 阻止字段被隐藏,且明确归属
Animal() {
System.out.println("Animal's name is: " + name); // 直接访问字段,不调用虚方法
}
}
class Dog extends Animal {
private final String name = "Dog"; // 字段名相同但无实际意义——已被父类 final 字段屏蔽
Dog() {
System.out.println("Dog's name is: " + name);
}
}⚠️ 注意:字段不存在多态,只有方法有多态;子类中声明同名字段(如本例 String name = "Dog";)属于字段隐藏(field hiding),而非重写,它不会影响父类对自身 name 的访问,但会因初始化时机问题导致上述陷阱。
总结:Animal's name is: null 并非 Bug,而是 Java 对象初始化语义与动态绑定机制共同作用的结果。理解这一机制,是编写健壮继承结构的基础。


















