Java多态的三种表现形式是:父类引用指向子类对象、方法重写后的动态分派、接口引用指向实现类对象;均依赖运行时实际类型通过vtable或itable动态绑定,且要求方法为非static、非final、非private的实例方法。

多态的三种常见表现形式
Java中对象的多态主要通过以下三种方式体现,本质都是编译时类型与运行时类型不一致:
-
父类引用指向子类对象:如
Animal a = new Dog();,调用a.speak()会执行Dog中重写的版本 -
方法重写(Override)后的动态分派:只有被
virtual语义覆盖的方法(非static、非final、非private)才参与多态 -
接口引用指向实现类对象:如
List,后续调用list = new ArrayList(); list.add()实际走的是ArrayList.add()
动态绑定发生的前提和时机
动态绑定(Dynamic Binding)不是自动对所有方法生效,它依赖 JVM 在运行期根据实际对象类型查虚方法表(vtable)来决定调用哪个版本。触发条件包括:
- 方法必须是非
static的实例方法(static方法绑定在编译期,属于静态绑定) - 方法不能是
final或private(二者无法被重写,JVM 直接内联或拒绝分派) - 调用必须通过引用变量发生,且该引用声明类型存在继承/实现关系(如
Object obj = new String();中obj.toString()是动态绑定,但obj.hashCode()同样适用,因为String重写了它)
容易被忽略的“假多态”陷阱
很多初学者误以为只要写了继承就一定有多态效果,其实以下情况看似像多态,实则不触发动态绑定:
-
static方法调用看的是引用声明类型,不是实际类型:class Parent { static void say() { System.out.println("Parent"); } } class Child extends Parent { static void say() { System.out.println("Child"); } } Parent p = new Child(); p.say(); // 输出 "Parent",不是 "Child" - 成员变量访问不具多态性:
p.name取的是Parent类中定义的name字段,哪怕Child也声明了同名字段,也不会被动态选择 - 构造器中调用被重写的方法,此时子类字段可能还未初始化,容易引发
NullPointerException或逻辑错误
如何验证是否发生了动态绑定
最直接的方式是打断点观察方法调用栈,或借助 javap -c 查看字节码中的调用指令:
立即学习“Java免费学习笔记(深入)”;
- 动态绑定对应
invokevirtual指令(绝大多数实例方法) - 静态绑定对应
invokestatic(static方法)、invokespecial(构造器、private、super.调用) - 接口方法调用是
invokeinterface,也属动态绑定,但查找机制不同(itable 而非 vtable)
真正理解多态,关键不在“能写出来”,而在清楚每一行代码背后是 invokevirtual 还是 invokestatic —— 这决定了它会不会随对象实际类型变化而变化。










