静态分派在编译期根据引用变量声明类型选择重载方法,动态分派在运行时依据实际对象类型查找重写方法;invokestatic/invokespecial指令不查表,invokevirtual指令触发虚方法表查找。

静态分派发生在编译期,靠的是变量声明类型
Java 编译器在重载(overload)解析时,不看 new 出来的实际对象是什么,只看方法调用处的**引用变量声明类型**。这就是为什么 say(Object) 和 say(String) 同时存在时,传一个 null 会编译报错——编译器无法从声明类型推断唯一候选方法。
- 常见错误现象:
ambiguous method call错误,尤其在传null或泛型擦除后类型信息丢失时高频出现 - 使用场景:所有重载方法的选择,比如
print(int)、print(String)、print(Object)的调用分支 - 参数差异:静态分派只比对形参的**编译期类型**,和实参运行时的
getClass()无关 - 性能影响:零开销,纯编译期行为,不影响字节码或运行时速度
动态分派依赖运行时的实际类型,由 invokevirtual 指令驱动
重写(override)的方法调用走的是动态分派。JVM 在运行时根据栈顶引用指向的**实际对象类型**,查该类的方法表(vtable),找到最终要执行的版本。这也是多态的核心机制。
- 常见错误现象:子类覆写了方法但没生效,往往是因为调用方持有的是父类引用且该方法未被正确重写(比如签名不一致、缺少
@Override导致变成重载) - 使用场景:所有非
static、非final、非private的实例方法调用,例如list.size()调用的是ArrayList.size()还是LinkedList.size() - 兼容性影响:JDK 8 引入默认方法后,接口中的
default方法也参与动态分派,但优先级低于类中定义的方法 - 注意:字段访问不参与动态分派,
obj.field始终按声明类型取值
invokestatic 和 invokespecial 不走动态分派
static 方法、构造器、私有方法、父类方法显式调用(super.xxx())都用 invokestatic 或 invokespecial 指令,它们在编译期就绑定了目标方法符号引用,运行时不会重新查找。
- 常见错误现象:以为
super.method()会触发子类重写逻辑,实际上它绕过动态分派,强制调用父类版本 - 使用场景:
String.valueOf(null)调用的是String类里的静态方法,哪怕你传的是Integer子类实例,也绝不会走到Integer.valueOf() - 性能影响:比
invokevirtual略快,少一次虚方法表查找,但现代 JVM 的内联优化已大幅缩小差距
final 方法能被内联,但本质仍是动态分派
final 实例方法不能被重写,所以 JIT 编译器在热点路径上大概率会直接内联它。但它在字节码层面仍用 invokevirtual,语义上还是动态分派——只是因为无子类覆盖,运行时查表结果恒定。
立即学习“Java免费学习笔记(深入)”;
- 容易踩的坑:误以为加了
final就等于“编译期绑定”,其实它和static有本质区别;反射仍可调用final方法,而static方法反射调用需注意类加载时机 - 验证方式:用
javap -c看字节码,final方法调用指令仍是invokevirtual,不是invokestatic - 真正影响分派行为的是方法是否可重写,而不是是否被内联








