java多态的核心在于编译期类型与运行期类型的分离:前者决定方法调用的合法性,后者决定实际执行的方法版本,二者不一致时通过动态绑定(vtable机制)实现行为差异化。

Java 中多态的实现,核心在于“编译期类型”和“运行期类型”的分离与协同。编译期类型决定**能调用哪些方法**(即语法合法性),运行期类型决定**最终执行哪个版本的方法**(即行为归属)。两者不一致时,多态才真正发生。
编译期类型:由声明决定,静态可见
编译期类型是变量在代码中声明时的类型,编译器仅据此检查方法是否存在、参数是否匹配、访问权限是否允许。它不关心 new 出来的到底是什么对象。
- 例如:Animal a = new Dog(); 中,a 的编译期类型是 Animal
- 因此 a.eat() 能通过编译,因为 Animal 类有 eat() 方法
- 但 a.bark() 会编译失败,即使 Dog 有 bark(),因为 Animal 没有该方法
- 重载方法的选择也只看编译期类型 + 实参类型,与实际对象无关
运行期类型:由 new 决定,动态生效
运行期类型是对象在堆内存中真实创建的类,JVM 在运行时根据它来定位具体方法实现。这个过程依赖虚方法表(vtable)和动态绑定机制。
- 同一句 a.eat(),若 a 指向 Dog 实例,就执行 Dog 的 eat();若指向 Cat 实例,就执行 Cat 的 eat()
- 前提是 eat() 在 Animal 中是非 private、非 static、非 final 的可重写方法
- JVM 通过对象头中的类元信息查到其运行期类,再查该类的 vtable,找到对应方法入口
- 子类重写父类方法时,会在自己的 vtable 中覆盖父类方法的槽位,从而实现行为替换
二者如何协作完成多态
多态不是单靠运行时“猜”,也不是纯靠编译时“定”。它是编译期检查 + 运行时分派的组合结果:
- 编译阶段做“准入检查”:确保调用合法(方法存在、参数匹配、权限足够)
- 字节码中生成 invokevirtual 指令,但目标方法符号未固化,留待运行时解析
- 运行阶段做“动态分派”:根据对象实际类型查 vtable,跳转到正确的实现
- final、static、private 方法没有 vtable 条目,只能静态绑定,无法参与运行期多态
一个典型误区:变量类型 ≠ 对象类型
很多人混淆“变量的类型”和“变量所指对象的类型”。前者是编译期类型,后者才是运行期类型。正是这种不一致,让同一个引用能表现出不同行为。
- Animal a; —— 编译期类型是 Animal,但 a 可以指向任何 Animal 子类实例
- a = new Dog(); —— 此时运行期类型是 Dog
- a = new Cat(); —— 此时运行期类型变成 Cat
- 只要方法签名一致且可重写,调用 a.sound() 就自动适配各自实现











