java中对象在构造器执行前已被创建,因此抽象类的实例初始化块可调用子类实现的抽象方法——此时对象已存在但尚未完全初始化,属危险但合法的行为。
java中对象在构造器执行前已被创建,因此抽象类的实例初始化块可调用子类实现的抽象方法——此时对象已存在但尚未完全初始化,属危险但合法的行为。
在Java面向对象模型中,一个常见的认知误区是:“构造器负责创建对象”。实际上,对象的内存分配与实例化发生在构造器调用之前,构造器仅负责初始化(initialization),而非实例化(instantiation)。这一底层机制直接解释了为何抽象类中的实例初始化块({ ... })能成功调用子类实现的抽象方法——哪怕子类自己的构造器尚未执行。
我们来看原始示例的执行时序:
abstract class Mammal {
{ // ← 实例初始化块:在 super() 返回后、Mammal 构造器体执行前运行
System.out.println(chew()); // ✅ 调用子类 Platypus 的 chew()
}
abstract CharSequence chew();
public Mammal() {
System.out.println("Mammal constructor"); // ← 此处才进入构造器主体
}
}
class Platypus extends Mammal {
public Platypus() {
System.out.println("Platypus constructor");
}
@Override
String chew() {
return "yummy!";
}
}
当执行 new Platypus() 时,JVM 执行以下严格顺序(依据 Java 语言规范 JLS §12.5):
- 分配内存:为 Platypus 对象分配堆空间,所有字段初始化为默认值(null, 0, false);
- 调用父类构造链:隐式插入 super() → 进入 Mammal() 构造器;
-
执行父类实例初始化块:Mammal 的 { System.out.println(chew()); } 被执行;
- 此时 this 指向正在构建的 Platypus 实例(类型为 Platypus,非 Mammal);
- chew() 是虚方法调用(invokevirtual),JVM 根据实际运行时类型 Platypus 查找并执行其重写版本;
- ⚠️ 但此时 Platypus 自身的字段尚未赋值、构造器体也未执行——对象处于部分初始化状态;
- 执行 Mammal 构造器体:输出 "Mammal constructor";
- 返回至 Platypus 构造器:执行其构造器体,输出 "Platypus constructor"。
因此,输出顺序为:
yummy! // 实例块中调用 chew() → Platypus.chew() Mammal constructor // Mammal() 构造器体 Platypus constructor// Platypus() 构造器体
✅ 为什么这是合法的?
- JVM 规范允许在对象创建后、任意构造器执行中调用实例方法;
- 多态机制不依赖“是否完全初始化”,只依赖对象的实际类型;
- 抽象方法本身无特殊限制——只要子类提供了实现,虚方法分派即可成功。
⚠️ 但这是高风险实践!关键注意事项:
- 字段可能为 null 或默认值:若 Platypus.chew() 访问了自身未初始化的 final String food; 字段,将得到 null,引发 NPE 或逻辑错误;
- 违反构造安全契约:构造过程中应避免暴露 this 引用或调用可被重写的方法(包括抽象方法),否则破坏封装性;
- 静态分析工具会告警:如 SonarQube、IDEA 会标记 Call to method 'chew' during object construction;
- 与模板方法模式冲突:标准模板方法(如 templateMethod() 调用 doStep())应明确设计为“构造后调用”,而非在初始化块中隐式触发。
✅ 推荐替代方案:显式、安全的初始化
若需在继承链中统一执行子类定制逻辑,应采用延迟初始化 + 模板方法模式:
abstract class Mammal {
private boolean initialized = false;
protected Mammal() {
// 空构造器,不执行业务逻辑
}
// 模板方法:由子类显式调用(如在构造器末尾)
final void initialize() {
if (initialized) return;
System.out.println(chew()); // ✅ 安全:此时子类字段已初始化
initialized = true;
}
abstract CharSequence chew();
}
class Platypus extends Mammal {
private final String favoriteFood = "worms";
public Platypus() {
super();
initialize(); // ← 显式、可控、安全
System.out.println("Platypus constructor");
}
@Override
String chew() {
return "yummy! " + favoriteFood; // ✅ favoriteFood 已赋值
}
}
总结
抽象类中调用抽象方法并非“魔法”,而是JVM对象生命周期与动态绑定机制共同作用的结果:对象先存在,再初始化;方法调用按实际类型分派,不等待构造完成。理解这一点,既可解惑异常行为,更提醒我们——在构造器及初始化块中调用可重写方法(无论抽象与否),本质上是在走钢丝。真正的健壮设计,应将可变逻辑推迟到对象完全构建之后,通过显式初始化协议来保障安全性与可维护性。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











