多态的“编译看左边,运行看右边”本质是编译期静态检查与运行期动态分派的配合,依赖虚方法表和对象头中的类指针实现;编译时按声明类型校验方法合法性,运行时通过实际对象的vtable调用重写方法。

多态的“编译看左边,运行看右边”,本质是编译期类型检查与运行期动态绑定的配合结果,底层依赖的是虚方法表(vtable)和动态分派(dynamic dispatch)机制。
编译期:只认声明类型(左边)
编译器根据变量的声明类型(即等号左边的类型)做静态检查:
- 决定哪些方法可以被调用——只能调用声明类型中定义或继承的方法;
- 检查参数类型、返回类型、访问权限是否合法;
- 不关心实际对象是什么,哪怕右边是子类实例,只要方法在父类中不存在,编译就报错。
例如:Animal a = new Dog();,编译器只确认a.bark()是否在Animal类中存在。若bark()是Dog独有方法,而Animal没有,则编译失败。
运行期:实际调用子类重写方法(右边)
当执行到一个被重写(override)的实例方法时,JVM 不直接跳转到声明类型的方法体,而是:
- 查对象实际所属类(new Dog() → 运行时确定是Dog类型);
- 通过该类的虚方法表,找到对应方法的真正入口地址;
- 如果子类重写了该方法,vtable 中就指向子类版本;否则指向父类版本。
所以a.sound()(假设sound()在Animal中定义、Dog中重写),最终执行的是Dog.sound()。
关键前提:必须满足三个条件
- 要有继承关系(或实现接口);
- 子类必须重写父类的非静态、非私有、非final的实例方法;
- 必须通过父类引用指向子类对象(即左边是父类/接口,右边是子类实现)。
不满足任一条件,就不会触发动态绑定。比如调用static方法、private方法或final方法,都是编译期绑定(早期绑定),和右边对象无关。
底层不是“看右边”而是“查对象头里的类指针”
JVM 中每个对象头都存有指向其实际类元数据(Klass)的指针。方法调用字节码(如invokevirtual)执行时,会:
- 从对象头拿到真实类信息;
- 查该类的虚方法表;
- 按方法签名索引到具体函数地址;
- 跳转执行。
所谓“运行看右边”,其实是透过对象头,定位到new操作符创建的那个真实类,再查它的行为定义。







