JVM中的静态分派与动态分派
下面给你一份JVM 层面 + Java 示例 + 字节码原理都讲清楚的《静态分派与动态分派》讲解。这是理解 Java 多态的核心关键。
🟥 一、什么是静态分派(Static Dispatch)
静态分派 = 编译阶段决定调用哪个方法。
特点:
发生在 编译期
依据 静态类型(变量声明类型)
用于 方法重载(overload)
与对象实际类型完全无关
在字节码中不体现重载逻辑,选择在编译器完成
✔ 静态分派的经典例子(方法重载)
class Human {}
class Man extends Human {}
class Woman extends Human {}
class Hello {
void say(Human h) { System.out.println("hello human"); }
void say(Man m) { System.out.println("hello man"); }
void say(Woman w) { System.out.println("hello woman"); }
}
public static void main(String[] args) {
Human h = new Man(); // 实际类型 = Man,静态类型 = Human
Hello hello = new Hello();
hello.say(h); // 调用哪一个?
}
输出:
hello human
❗为什么不是 "hello man"?
因为 重载选择完全由静态类型决定:
h 的静态类型 = Human
编译器选择:
say(Human)
写入字节码中。
运行期不会重新选择。
🔍 静态分派 = 编译器执行“多次选择”(选择参数类型最合适的版本)
这被经典称为:
多分派(multiple dispatch)在编译期完成
参与分派的因素:
变量的静态类型
对应重载方法的参数类型列表
JVM 字节码中调用重载方法只看到:
invokevirtual # say(LHuman;)V
已经确定了。
🟦 二、什么是动态分派(Dynamic Dispatch)
动态分派 = 运行阶段决定调用哪个方法。
特点:
发生在 运行期
依据 实际类型(对象真正的类型)
用于 方法重写(override)
与静态类型无关(只要静态类型有该方法即可)
JVM 通过 虚方法表(vtable) 定位最终执行的方法
✔ 动态分派经典例子(方法重写)
class Human {
void say() { System.out.println("human"); }
}
class Man extends Human {
@Override
void say() { System.out.println("man"); }
}
public static void main(String[] args) {
Human h = new Man(); // 实际类型 = Man
h.say(); // 调用哪个 say()?
}
输出:
man
❗为什么?
因为 JVM 会根据对象的 实际类型 Man 所指向的 vtable 映射到 Man.say()。
🔍 动态分派 = 依据实际类型进行虚方法的运行期选择
执行:
invokevirtual Human.say
JVM 执行步骤(简化版):
根据引用找到实际对象:
Man根据对象的 klass pointer → vtable
找到被 override 的方法入口
调用:
Man.say()
🟥 静态分派 vs 🟦 动态分派:对照总结
🧠 一个结合两者的经典例子
你可以看到静态和动态分派同时发生:
class Human {
void say(Human h) { System.out.println("Human say human"); }
void say(Man m) { System.out.println("Human say man"); }
void who() { System.out.println("I am Human"); }
}
class Man extends Human {
@Override
void who() { System.out.println("I am Man"); }
void say(Man m) { System.out.println("Man say man"); }
void say(Human h) { System.out.println("Man say human"); }
}
public static void main(String[] args) {
Human h = new Man(); // 静态类型 = Human,实际类型 = Man
Man m = new Man();
h.say(m); // 静态分派决定调用哪个 say?
h.who(); // 动态分派决定最终执行哪个 who?
}
分析:
① 静态分派
h.say(m)
编译器看到:
h: Human
m: Man
所以重载选择 Human 的:
say(Man)
② 动态分派
实际运行调用的 say() 版本,要看 h 的实际类型:
h 的实际类型 = Man
所以最终调用 Man 的:
Man.say(Man)
③ who()
调用的是运行时的:
Man.who()
🔥 最终总结(最重要的三句话)
1️⃣ 重载(overload)= 静态分派,编译期根据静态类型决定
2️⃣ 重写(override)= 动态分派,运行期根据实际类型决定
3️⃣ 编译看左边(静态类型),运行看右边(实际类型)
如果你愿意,我还能继续给你:
🔹 Java 字节码级别的静态分派示例
🔹 invokevirtual、invokespecial、invokeinterface 的动态分派机制
🔹 JVM vtable / itable 的结构图
🔹 专门可视化“静态分派 vs 动态分派”的流程图
需要哪一部分?