Java多态通过动态分派实现,即运行时根据对象实际类型(而非声明类型)决定调用哪个重写方法;依赖invokevirtual指令和虚方法表,仅适用于非static、非private、非final的实例方法。

Java 多态通过动态分派机制运行代码,核心在于:方法调用的实际目标,在运行时才根据对象的真实类型(而非声明类型)确定。这正是 override(重写)能实现多态的关键。
动态分派发生在方法重写场景中
当子类重写了父类的非私有、非静态、非 final 的实例方法,且通过父类引用调用该方法时,JVM 不在编译期绑定具体实现,而是在运行期查对象的实际类型,再决定执行哪个版本的方法。
例如:
class Animal { void speak() { System.out.println("Animal sound"); } }
class Dog extends Animal { @Override void speak() { System.out.println("Woof!"); } }
class Cat extends Animal { @Override void speak() { System.out.println("Meow!"); } }
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.speak(); // 输出 "Woof!"
a2.speak(); // 输出 "Meow!"
虽然 a1 和 a2 的静态类型都是 Animal,但 JVM 在执行 a1.speak() 时,会:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
- 检查
a1所指向对象的实际类型(Dog) - 查找
Dog类的方法表(vtable),定位speak()方法的入口地址 - 跳转并执行
Dog.speak()
这个过程就是动态分派,由 invokevirtual 字节码指令驱动。
动态分派依赖 invokevirtual 指令和虚方法表(vtable)
- 编译后,
a1.speak()被翻译为invokevirtual Animal.speak:()V - 运行时,JVM:
- 获取
a1对象头中的类元数据指针 - 定位该类(
Dog)的虚方法表 - 根据方法签名(
speak())在表中查找对应槽位 - 取出该槽位存储的实际方法地址并调用
- 获取
注意:只有被
invokevirtual调用的方法才参与动态分派。static、private、final方法使用invokestatic或invokespecial,在类加载阶段就解析完成,不参与运行时分派。
动态分派是单分派的
它只依据一个宗量(argument)做判断:接收者的实际类型(即 a1 是 Dog 还是 Cat)。
不关心参数类型、不比较多个对象类型——这与静态分派(需同时看接收者静态类型 + 参数静态类型)形成对比。
实际开发中要注意的点
- 父类引用调用重写方法 → 走动态分派 → 正确体现多态
- 父类引用调用重载方法 → 走静态分派 → 只看参数的静态类型,容易误判
-
static方法看似“被重写”,实则只是隐藏(hiding),调用完全按静态类型,不走动态分派 -
final方法虽用invokevirtual,但因不可覆盖,JVM 可能内联优化,逻辑上仍属“静态可定”
不复杂但容易忽略
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










