“编译看左边,运行看右边”指java中方法调用的静态检查与动态分派机制:编译时依据引用声明类型检查合法性,运行时根据实际对象类型通过虚方法表动态绑定重写的方法,但仅适用于非static、非private、非final的实例方法。

“编译看左边,运行看右边”不是一句玄学口诀,而是 Java 虚拟机对方法调用进行静态检查与动态分派的真实体现。它的背后,是编译器和 JVM 各司其职、协同工作的结果——前者保障代码安全合法,后者确保行为准确灵活。
编译阶段:只认声明类型,不问实际是谁
编译器看到 Animal a = new Dog();,只关心 a 的声明类型是不是 Animal,以及你调用的方法(比如 a.shout())在 Animal 类中是否存在、签名是否匹配、访问权限是否允许。
- 如果
shout()在Animal中不存在,哪怕Dog里有,也会直接报错; - 如果调用的是
a.bark()(而Animal没定义),编译失败,哪怕Dog确实有这个方法; - 它不会去查
new Dog()创建了什么,也不管a实际指向哪个类的实例。
运行阶段:靠对象头+虚方法表,精准定位实现
真正执行 a.shout() 时,JVM 不会按 Animal 的字节码跳转,而是:
- 从
a所指对象的对象头中,取出真实类的元数据指针(指向Dog的Klass结构); - 查
Dog类的虚方法表(vtable),根据方法签名(如shout())索引到具体函数入口; - 若
Dog重写了该方法,vtable 条目就指向Dog.shout()的字节码;否则沿继承链向上找,最终可能落到Animal.shout()。
这个过程叫 动态分派,只对普通实例方法生效,且前提是该方法可被重写(非 static、private、final)。
哪些情况不走“运行看右边”?
不是所有调用都享受多态待遇。以下情形,编译和运行都只看左边(声明类型):
-
静态方法:如
Animal.sleep(),即使子类定义同名方法,也只是隐藏(hiding),调用完全由引用类型决定; -
成员变量:访问
a.name时,读取的是Animal类中定义的字段,子类同名字段不会覆盖,也不参与动态绑定; - 私有或 final 方法:它们无法被重写,编译期就绑定了目标,JVM 直接调用声明类中的版本。
关键前提缺一不可
想触发“运行看右边”,必须同时满足:
- 存在继承或实现关系;
- 子类确实重写了父类的非静态、非私有、非 final 实例方法;
- 使用父类(或接口)引用指向子类实例,即
父类 ref = new 子类();。
三者少一个,“多态”就失效,调用行为就会退回到静态绑定。











