JVM 中的静态类型和实际类型
下面用非常清晰、非常标准的 JVM 术语帮你解释一下 静态类型(Static Type) 与 实际类型(Actual Type / Runtime Type) 的概念,并说明它们在 JVM 中扮演什么角色、为什么需要区分它们。
✅ 一、静态类型(Static Type)
静态类型 = 编译阶段看到的类型
也称为:编译时类型、声明类型
在 Java 中,变量、参数、字段的类型都是写在代码里的,例如:
Animal a = new Dog();
其中:
a 的静态类型 = Animal
静态类型在编译期就确定,不会随执行改变。
编译器通过静态类型决定:
✔ 能否调用某个方法(方法是否存在)
例如 a.bark() 会报错,因为在静态类型 Animal 中没有 bark 方法。
✔ 方法重载(overload)选择
编译器只根据静态类型选重载版本。
这也是 Java 重载不支持根据运行时类型选择的原因。
✔ 字节码指令生成(特别是 invokevirtual 的符号引用)
比如:
a.run();
编译器会把 run() 解析为一个方法符号引用:
Animal.run()
最终写入字节码的也会是 Animal 的方法符号引用。
✅ 二、实际类型(Actual Type / Runtime Type)
实际类型 = 运行过程中对象真正的类型
也称为:运行时类型、动态类型
在上边例子:
Animal a = new Dog();
实际类型:
a 的实际类型 = Dog
实际类型是在运行时才真正确定的(对象在堆中新建的时候)。
JVM 的运行时方法调用使用实际类型进行动态绑定(Dynamic Dispatch):
Dog.run()
JVM 会根据对象实际类型的 vtable 找到最终执行的方法。
✨ 这样你就能清楚区分:
✅ 三、为什么 JVM 必须同时有它们?
✔ 1. 为了支持静态语言的编译检查
静态类型决定你能不能写某段代码,比如能不能调用方法、访问字段。
✔ 2. 为了支持运行时多态、动态分派
实际类型让 JVM 能做到 Java 最重要的特性:虚方法调用(invokevirtual)走重写方法。
✅ 四、例子:静态类型与实际类型不一样会怎样?
示例代码
Animal a = new Dog();
a.run(); // Animal.run()? Dog.run()?
编译器通过静态类型 Animal 生成字节码:
invokevirtual Animal.runJVM 执行时根据实际类型 Dog 查找实现:
Dog.override_run()
→ 最终调用的是 Dog.run()(多态)
🔥 五、再来一个更能说明区别的例子:方法重载 vs 重写
重载(静态分派)只看静态类型:
void hello(Animal a)
void hello(Dog d)
Animal a = new Dog();
hello(a); // 调用 hello(Animal) 而不是 hello(Dog)
因为静态类型是 Animal。
重写(动态分派)只看实际类型:
Animal a = new Dog();
a.run(); // 调用 Dog.run() (实际类型 Dog)
📌 总结一句话
静态类型决定编译行为,实际类型决定运行行为。
编译看左边,运行看右边:
Animal a = new Dog();
编译阶段:看左边(Animal) → 静态类型
运行阶段:看右边(Dog) → 实际类型