动态绑定指运行时根据对象实际类型而非引用类型决定调用哪个方法,仅适用于可被重写(非static、非private、非final)的实例方法,通过invokevirtual指令实现;字段访问则始终依据引用类型,不参与动态绑定。

动态绑定就是“运行时看对象真实类型,不是看变量声明类型”
Java中,当你写 a.makeSound(),而 a 是 Animal a = new Dog() 这种写法时,真正执行的是 Dog.makeSound() ——不是因为 a 声明为 Animal,而是因为堆里那个对象确实是 Dog 实例。这个“临门一脚”的决策发生在程序运行时,由 JVM 查对象的虚方法表(vtable)完成,这就是动态绑定的本质。
哪些方法会触发动态绑定?关键看能不能被重写
只有满足全部条件的实例方法才走动态绑定:
- 必须是非
static的(静态方法绑定在编译期,看引用类型) - 不能是
private(子类根本看不见,谈不上重写) - 不能是
final(语言层禁止覆盖,JVM 直接内联或硬编码调用) - 必须是被子类正确
@Override的(签名一致、访问权限不更严格)
反例:toString() 默认可被重写,所以 new Dog().toString() 调的是 Dog 版本;但 Dog.class.getName() 是 static 方法,哪怕 Dog 里也写了个同名 static 方法,调用仍取决于左边引用类型,不是动态绑定。
字段访问永远不参与动态绑定,这是最容易踩的坑
很多人以为 Animal a = new Dog() 后,a.i 也会取 Dog.i,其实不会。字段访问只看引用声明类型——a 是 Animal 类型,就去 Animal 类里找 i,哪怕 Dog 里定义了同名字段,也只是隐藏(hiding),不是覆盖(overriding)。
立即学习“Java免费学习笔记(深入)”;
示例:
class Animal { int i = 10; }
class Dog extends Animal { int i = 20; }
Animal a = new Dog();
System.out.println(a.i); // 输出 10,不是 20
想让值随对象类型变化?必须通过方法:把 i 封装进 getI(),再让子类重写它——方法才走动态绑定。
字节码层面一眼识别:invokevirtual 才是动态绑定的标志
编译后,动态绑定的方法调用生成 invokevirtual 指令;而 static 方法是 invokestatic,private 或构造器是 invokespecial。你可以用 javap -c 看字节码验证:
javap -c Test.class | grep invokevirtual
如果看到 invokevirtual,说明这个调用是留给运行时决定的;如果没看到,那基本就是编译期绑死的——比如你误以为子类重写了 static 方法,实际只是隐藏,字节码里仍是 invokestatic。
真正难调试的,往往是字段和方法混用时的预期错位:比如父类方法里用了 this.i,你以为它会随子类对象变,结果它根本不变;而同一行里的 this.getI() 却会变。这种差异不在语法上,而在绑定机制底层,得靠理解 vtable 和字段/方法分离设计才能稳住判断。










