
java 在类加载阶段就已确定方法重写关系,与对象实例化顺序无关;所有实例共享类元数据,new sub() 创建的对象从诞生起就是 sub 类型,其方法调用始终遵循运行时多态规则。
java 在类加载阶段就已确定方法重写关系,与对象实例化顺序无关;所有实例共享类元数据,new sub() 创建的对象从诞生起就是 sub 类型,其方法调用始终遵循运行时多态规则。
在 Java 中,方法重写(Override)的解析机制并非发生在运行时“逐层扫描父类查找子类实现”,也不是依赖构造器执行顺序动态决定的——而是由 JVM 类加载与链接阶段 静态确立的。关键在于:类的元数据(包括方法表 vtable)在类首次被加载、验证、准备和解析后即已固化,与任何对象是否被创建完全无关。
类加载时即确定重写关系
当 JVM 加载 Sub.class 时,会解析其继承关系,并构建该类的 虚方法表(virtual method table, vtable)。vtable 是一个数组,每个可重写方法(非 private / static / final)占据一个槽位,槽中存放的是该类实际应调用的方法入口地址。对于 m() 方法:
- Super 的 vtable 中,m() 槽指向 Super.m();
- Sub 的 vtable 中,m() 槽被替换为 Sub.m() 的地址——这一决策在 Sub 类加载完成时即已确定。
可通过反射验证这一点,无需创建任何实例:
public class Sub extends Super {
@Override public void m() { }
public static void main(String[] args) throws Exception {
// 即使未 new Sub(),也能获取方法声明类
System.out.println(Sub.class.getMethod("m").getDeclaringClass()); // 输出: class Sub
System.out.println(Super.class.getMethod("m").getDeclaringClass()); // 输出: class Super
}
}
这证明:重写关系是类的静态属性,由编译器生成字节码时定义,由 JVM 在类加载时解析并固化。
对象创建:类型恒定,状态未初始化
执行 new Sub() 时,JVM 执行两阶段操作:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
内存分配与元数据绑定
分配足够容纳 Sub 全部字段(含继承自 Super 的字段)的内存块,并将对象头中的 类指针(klass pointer) 指向 Sub.class 的元数据区。从此刻起,该对象在 JVM 中永远是 Sub 类型——getClass() 返回 class Sub,多态分派永远基于此类型。-
构造器链式执行(按继承链自顶向下)
- 先调用 Super() 构造器(隐式或显式 super());
- Super() 内部调用 m() → JVM 查 this 对象的类指针为 Sub → 查 Sub 的 vtable → 跳转至 Sub.m();
- 此时 Sub 的字段(如 Person p)尚未初始化(仍为 null),但 Sub.m() 已可安全调用(只要不访问未初始化字段);
- Super() 返回后,才执行 Sub 的字段初始化(p = new Person())和构造器剩余逻辑。
这就是为何输出中先出现 "Method m() of class Sub" 再抛出 NullPointerException:方法重写由对象类型决定,而字段初始化由构造器执行顺序保证——二者解耦。
重要实践警示
❗ 避免在构造器中调用可重写方法
因为子类字段尚未初始化,可能导致 NPE 或逻辑错误(如上例中 p.getName())。若必须调用,应声明为 final 或 private,确保不可重写。-
✅ 安全替代方案:
class Sub extends Super { private final Person p = new Person(); // final 字段在 super() 后立即初始化 Sub() { super(); // 显式调用,强调顺序 System.out.println("Sub constructor"); m(); // 此时 p 已初始化 } } ? 理解 getClass() 的行为:
即使在 Super() 构造器中调用 this.getClass(),返回的仍是 Sub.class——再次印证对象类型在分配内存时即已锁定。
总之,Java 的多态不是“运行时动态查子类”,而是基于已加载类的 vtable 的高效直接跳转。理解这一机制,有助于规避构造器陷阱,并写出更健壮的继承代码。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










