Java多态调用依赖动态绑定,编译看引用类型、运行看实际对象类型,通过虚方法表实现;仅非static、非private、非final的实例方法参与分派,字段、static、private、final及构造方法均不支持动态绑定。

Java 中多态调用的动态绑定与方法分派,核心在于“编译看引用类型,运行看实际对象类型”。它不是语法糖,而是 JVM 通过虚方法表(vtable)在运行时完成的精准跳转。理解这一逻辑,才能避开字段访问、static 方法、构造中调用重写方法等典型陷阱。
动态绑定只对虚方法生效
只有满足条件的实例方法才参与运行时分派:
- 必须是非 static、非 private、非 final 的普通实例方法
- 子类重写时需严格遵循契约:方法签名一致、返回类型协变、访问权限不收紧、检查异常不扩大
- 强烈建议使用 @Override 注解——它让编译器帮你校验是否真在重写,而非意外重载或拼写错误
方法调用分两步走:编译期检查 + 运行时分派
以 Animal a = new Dog(); a.makeSound(); 为例:
- 编译阶段:检查 Animal 类是否存在 makeSound() 方法声明;若不存在,直接报错
- 运行阶段:JVM 查 Dog 实例的虚方法表,定位到 Dog 类中重写的 makeSound() 地址并执行
- 哪怕后续把 new Dog() 换成 new Cat(),同一行代码自动调用 Cat 版本,调用方完全无感
这些情况不走动态绑定,容易误判
看似是多态写法,但行为不符合预期,往往是因为用了不支持动态绑定的成员:
立即学习“Java免费学习笔记(深入)”;
- 字段访问永远按引用类型:a.name 取的是 Animal 类定义的 name 字段,哪怕 Dog 也定义了同名字段,也不会被访问
- static 方法调用取决于引用类型:Animal.log() 和 Dog.log() 是两个独立方法,不会重写,也不会动态分派
- private / final / 构造方法不参与重写:它们在编译期就绑定目标,子类中同名方法只是新定义,与父类无关
向上转型是触发多态的前提
必须通过父类(或接口)类型引用指向子类对象,才能激活动态绑定:
- 正确形式:Animal a = new Dog();、List<String> list = new ArrayList<>();
- 错误形式:Dog d = new Dog(); d.makeSound(); —— 这是静态绑定,即使 Dog 重写了方法,也不体现多态价值
- 多态真正发挥作用的场景:参数传入(void feed(Animal a))、集合统一处理(for (Animal a : zoo) a.eat();)、策略替换(新增 Duck 类无需改原有循环)


















