
java 在对象创建前就已完成类加载与方法表构建,因此父类构造器中调用的虚方法能准确分发到子类重写版本——这依赖于运行时类型(而非构造阶段)和 jvm 的虚方法表(vtable)机制,与字段初始化顺序无关。
java 在对象创建前就已完成类加载与方法表构建,因此父类构造器中调用的虚方法能准确分发到子类重写版本——这依赖于运行时类型(而非构造阶段)和 jvm 的虚方法表(vtable)机制,与字段初始化顺序无关。
在 Java 中,方法重写(@Override)的动态绑定并非发生在对象实例化过程中,而是由 JVM 在类加载阶段就已确定的静态结构所支撑。关键在于:类的元数据(包括继承关系、方法签名、重写映射)在首个实例创建前就已解析并固化。
当执行 new Sub() 时,JVM 的实际步骤如下:
-
类加载与链接(Class Loading & Linking)
Sub.class 被加载、验证、准备(为静态字段赋默认值)、解析(建立符号引用与直接引用的映射)。此时 JVM 已明确:- Sub 继承自 Super
- Sub.m() 是对 Super.m() 的重写
- 所有 Sub 实例的 m() 调用应指向 Sub.m()(除非被进一步重写)
内存分配与对象头初始化(Object Allocation)
JVM 为 Sub 实例分配足够内存(含 Super 和 Sub 的所有实例字段),并将对象头(Object Header)中的类指针(klass pointer) 指向 Sub.class 的元数据区。该指针在对象诞生瞬间即确定,且不可更改——这意味着:即使 Super 构造器正在执行,this 的运行时类型始终是 Sub。-
构造器链执行(Constructor Chaining)
- 首先调用 Super():此时 this 是一个“半初始化”的 Sub 实例(所有字段为默认值:p == null, value == 0 等)。
- Super() 中调用 m() → JVM 查找 this.getClass()(即 Sub.class)的虚方法表(vtable),发现 m() 条目指向 Sub.m() → 动态分发成功。
- Sub.m() 执行时访问 p.getName(),但 p 尚未初始化(Person p = new Person() 的赋值语句尚未执行),故抛出 NullPointerException。
✅ 正确理解:不是“Java 在构造时扫描子类”,而是“每个对象从诞生起就携带其确切运行时类型,JVM 始终按此类型查表分发虚方法”。
Alibabacloud Sdk Client Initialization For Java下载在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
以下代码可验证该机制:
class Super {
Super() {
System.out.println("Runtime type: " + getClass().getSimpleName()); // 输出 "Sub"
method();
}
void method() { System.out.println("Super.method"); }
}
class Sub extends Super {
int value = 42;
@Override
void method() {
System.out.println("Sub.method, value = " + value); // 输出 "Sub.method, value = 0"
}
}
输出:
Runtime type: Sub Sub.method, value = 0
说明:
- getClass() 返回 Sub,证明运行时类型在构造开始前已确定;
- value 为 0(而非 42),印证字段初始化(value = 42)发生在 super() 返回之后、Sub 构造体执行之前。
⚠️ 重要实践警告:
永远避免在构造器中调用可被重写的方法(public/protected 非 final 方法)。因为此时子类字段尚未初始化,极易引发 NullPointerException 或逻辑错误。若必须调用,请将方法声明为 private 或 final,确保编译期静态绑定。
总结:Java 的多态性根植于类元数据的早期构建与对象头的不可变类型标识,而非构造过程的“实时扫描”。理解这一点,是写出健壮面向对象代码的关键前提。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











