动态分派本质是jvm运行期根据对象实际类型查虚方法表确定重写方法,仅适用于非private/static/final的实例方法;重载则在编译期按参数静态类型完成静态分派,与动态分派无关。

Java 中的多态动态分派机制,本质是 JVM 在运行时决定调用哪个方法实现的过程。它只对重写(override)生效,而与重载(overload)无关——重载在编译期就已确定,不参与动态分派。
重载:纯编译期行为,走静态分派
重载方法的选择完全由参数的编译期类型(即声明类型)决定,和实际对象类型无关。编译器根据方法签名(方法名 + 参数类型 + 参数个数 + 参数顺序)在编译阶段就锁定调用目标,生成对应的 invokestatic 或 invokevirtual 指令(注意:即使调用的是实例方法,重载选择本身仍是静态的)。
- 例如:
Object obj = new String("hello"); print(obj);会调用print(Object),哪怕print(String)存在——因为obj的声明类型是Object - 泛型擦除后,
List<string></string>和List<integer></integer>都变成List,可能意外匹配同一个重载版本 - 自动装箱会引入多个候选(如传
int可能匹配method(int)、method(long)、method(Integer)),编译器按优先级选,但易出错
重写:动态分派的核心,运行时按实际类型查虚方法表
只有满足条件的实例方法才参与动态分派:非 private、非 static、非 final,且在继承链中被子类重新提供实现。JVM 在运行时通过对象的实际类型查找其虚方法表(vtable),定位到最终执行的方法体。
- 调用指令是
invokevirtual,它不依赖引用类型,而依赖堆中对象的真实 class - 子类重写父类方法后,即使使用父类引用指向子类对象(如
Animal a = new Dog(); a.speak();),执行的仍是Dog.speak() - 协变返回类型允许子类重写时缩小返回类型(如父类返回
Animal,子类可返回Dog),仍属合法重写
重载 + 重写共存时:先静态,后动态
当一个调用既涉及重载又涉及重写(比如父类引用调用多个重载方法中的某一个,而该方法又被子类重写),JVM 分两步处理:
- 第一步(编译期):根据参数的声明类型选出重载版本(静态分派)
- 第二步(运行期):对该重载版本的方法,再根据对象的实际类型决定执行哪个重写实现(动态分派)
- 例如:
show(List list)和show(ArrayList list)同时存在;传入new ArrayList()调用show(ArrayList);但若写成List l = new ArrayList(); show(l);,则只能匹配show(List),之后再看l实际是不是子类重写了这个show(List)
不参与动态分派的常见陷阱
以下情况看似像重写,实则不走动态分派,容易引发预期外的行为:
-
static方法:同名只是“隐藏”,调用目标由引用类型决定,属于静态分派 -
private方法:子类中同名方法是全新方法,父类无法访问,更谈不上重写或分派 -
final方法:禁止重写,子类无法提供新实现,自然无动态分派可言 - 构造器、
super.xxx():使用invokespecial指令,解析阶段就绑定,不参与运行时分派
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











