
本文深入解析java单继承场景中,子类构造器执行时父类构造器如何调用被重写的方法,以及为何此时子类字段尚未初始化导致返回null——揭示jvm对象初始化的真实时序。
本文深入解析java单继承场景中,子类构造器执行时父类构造器如何调用被重写的方法,以及为何此时子类字段尚未初始化导致返回null——揭示jvm对象初始化的真实时序。
在Java的单继承体系中,子类对象的创建遵循严格而固定的初始化顺序:先父类后子类、先字段默认初始化再显式初始化、先父类构造器执行再子类构造器执行。这一顺序直接决定了你遇到的经典输出现象:
Animal's name is: null Dog's name is: Dog
? 核心原因:动态绑定 + 初始化时序错位
当执行 new Dog() 时,JVM按以下步骤操作:
- 为Dog对象分配内存(此时所有字段(包括name)被赋予默认值:null、0、false等);
- 隐式调用super() → 进入Animal构造器;
- Animal构造器中执行 System.out.println("Animal's name is: " + getName());;
- 此时虽在Animal构造器内,但当前对象是Dog实例,且Dog.getName()已重写Animal.getName() → JVM基于运行时类型(Dog)进行动态方法分派(Dynamic Dispatch),因此实际调用的是Dog.getName();
- 然而,Dog.name = "Dog" 这一显式初始化语句尚未执行(它发生在Animal构造器返回、进入Dog构造器体之前);
- 所以Dog.getName()返回的是Dog.name的默认值 —— null。
✅ 关键结论:重写的方法在父类构造器中被调用是完全合法且符合JVM规范的,但此时子类的字段初始化还未发生,访问子类字段将得到默认值(如null)。
? 验证代码:观察初始化时机
class Animal {
String name = "Animal";
Animal() {
System.out.println("[Animal ctor] before getName(): name = " + name); // → "Animal"
System.out.println("[Animal ctor] getName() returns: " + getName()); // → null(因调用Dog.getName)
System.out.println("[Animal ctor] after getName(): name = " + name); // → "Animal"
}
String getName() { return name; }
}
class Dog extends Animal {
String name = "Dog"; // ← 此行初始化发生在Animal构造器执行完毕之后!
Dog() {
System.out.println("[Dog ctor] getName() returns: " + getName()); // → "Dog"
}
@Override
String getName() { return name; }
}
输出:
[Animal ctor] before getName(): name = Animal [Animal ctor] getName() returns: null [Animal ctor] after getName(): name = Animal [Dog ctor] getName() returns: Dog
⚠️ 重要警告:避免在构造器中调用可重写方法
这是Java开发中的严重设计陷阱(《Effective Java》Item 17 明确指出):
- 构造器中调用this.xxx()若被子类重写,子类方法将操作一个半初始化的对象;
- 可能引发NullPointerException、逻辑错误或难以调试的状态不一致;
- 若必须复用逻辑,请将方法声明为private、static或final——它们无法被重写,从而规避动态绑定。
✅ 安全改写示例:
class Animal {
private final String name = "Animal"; // 使用final确保不可变
Animal() {
System.out.println("Animal's name is: " + getName()); // 调用private方法 → 绑定到Animal
}
private String getName() { return name; } // private → 静态绑定,不会被重写
}
class Dog extends Animal {
private final String name = "Dog";
Dog() {
System.out.println("Dog's name is: " + getName()); // 仍调用Animal.private getName()
}
}
✅ 总结
| 现象 | 原因 | 解决方案 |
|---|---|---|
| Animal构造器中getName()返回null | 动态绑定调用Dog.getName(),但Dog.name尚未初始化 | 避免在构造器中调用非private/final方法 |
| 期望输出"Animal"未出现 | Animal.name虽已初始化,但getName()被重写,不再访问本类字段 | 使用private/final方法,或重构为组合模式 |
理解这一机制,不仅能解释看似“反直觉”的输出,更是写出健壮、可继承类的关键基础。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











