静态绑定在编译期确定调用目标,基于声明类型,适用于static、private、构造器和final方法;动态绑定在运行时根据实际类型选择方法,仅适用于非static、非private、非final的重写实例方法。

静态绑定发生在编译期,只看声明类型
Java 中的 static 方法、private 方法、构造方法和 final 方法,全部在编译时就确定了调用目标——也就是“绑”在了变量的**声明类型**上,跟实际运行时对象是谁无关。
常见错误现象:Parent p = new Child(); p.staticMethod(); 看起来像多态,但其实调用的是 Parent.staticMethod(),哪怕 Child 里重写了同签名的 static 方法,也不会被触发。
- 静态绑定不依赖对象实例,甚至可以写成
Parent.staticMethod()(推荐写法) -
private方法隐式final,子类里同名方法只是新定义,不是重写 - 如果父类声明类型没有某个
public实例方法,即使子类有,p.method()也会编译报错
动态绑定依赖运行时的实际类型
只有满足「非静态、非私有、非 final」的实例方法,才会走动态绑定。JVM 在运行时根据对象的 实际类型(即 new 出来的那个类),查该类的方法表(vtable),找到最终执行的方法版本。
典型场景:Animal a = new Dog(); a.speak(); 调用的是 Dog.speak(),不是 Animal.speak()。
立即学习“Java免费学习笔记(深入)”;
- 动态绑定的前提是:方法在父类中已存在(至少是声明过),且子类做了重写(
@Override) - 子类新增而父类没有的方法,无法通过父类引用调用,编译直接失败
- 字段(
field)不参与动态绑定,a.name永远取Animal类里的name,哪怕Dog也定义了同名字段
从字节码看绑定差异
编译后,静态绑定的方法调用指令是 invokestatic,动态绑定的是 invokevirtual。你可以用 javap -c 查看:
public class Test {
static void s() {}
void v() {}
public static void main(String[] args) {
Test t = new Test();
t.s(); // → invokestatic
t.v(); // → invokevirtual
}
}
关键点在于:invokevirtual 指令本身不指定具体实现类,它靠栈顶对象的实际类型 + 方法签名去查表;而 invokestatic 直接锁定类和方法符号。
-
invokespecial用于构造器、private和super.xxx(),也是静态绑定语义 -
invokeinterface表面像动态,但本质仍是运行时查表,只是接口方法表结构不同
容易被忽略的陷阱:重载 vs 重写
重载(overload)是静态绑定,重写(override)才是动态绑定。很多人误以为“参数不同就自动多态”,其实不然。
例如:
class A { void m(Object o) { System.out.println("A-Object"); } }
class B extends A { void m(String s) { System.out.println("B-String"); } }
A a = new B();
a.m("hello"); // 编译报错!因为 A 里没有 m(String)
这里 B.m(String) 是对 A 的重载,不是重写,所以父类引用看不到它。
- 重载解析完全在编译期完成,依据是:变量声明类型 + 实参的编译期类型
- 重写必须方法签名(含返回类型协变)完全一致,且访问权限不能更严格
- 泛型擦除后,
List和List的方法签名一样,无法构成重载










