多态的本质是运行时动态绑定:调用方法取决于new出的实际对象类型而非变量声明类型,JVM通过对象头的类元信息查vtable跳转子类实现,仅实例方法参与,static/final/构造器/字段按编译时类型解析。

多态的本质是运行时动态绑定:调用哪个方法,不看变量声明类型,而看 new 出来的实际对象类型。
为什么写 Animal a = new Dog(); 后调用 a.makeSound() 会执行 Dog 的逻辑?
因为 JVM 在运行时查的是对象头里的类元信息(即“它到底是谁”),不是看左边的 Animal 声明。编译器只确保 Animal 类里有 makeSound() 方法(否则编译失败);真正执行哪份代码,由堆中那个对象的运行时类型决定。
- 编译阶段检查:
Animal是否有该方法签名 → 有,放行 - 运行阶段分派:JVM 查
a指向的对象内存布局 → 发现是Dog实例 → 跳转到Dog.makeSound()字节码 - 这个过程叫“虚方法调用”(invokespecial / invokevirtual),底层靠方法表(vtable)实现
没重写、没继承、没向上转型,就不是多态
这三个条件缺一不可,少一个就会掉回“静态调用”或编译报错:
-
Animal a = new Animal();→ 调用父类方法,不是多态(无子类参与) -
Dog d = new Dog(); d.makeSound();→ 编译时已锁定Dog,不涉及父类引用,不算多态 -
Animal a = new Dog(); a.eat();(eat()未被Dog重写)→ 仍算多态,只是执行了父类默认实现
常见踩坑:父类引用不能直接访问子类独有成员
比如 Animal a = new Cat(); a.catchMouse(); 会编译报错,因为 catchMouse() 不在 Animal 的可见接口里。这不是多态失效,而是编译器严格按引用类型做静态检查。
立即学习“Java免费学习笔记(深入)”;
- 想调用子类特有方法?必须先强制转型:
((Cat) a).catchMouse(); - 但要加
instanceof防ClassCastException:if (a instanceof Cat) ((Cat) a).catchMouse(); - 更安全的做法是:把共性行为抽象进父类或接口,避免频繁转型
class Animal {
public void makeSound() { System.out.println("动物发声"); }
}
class Cat extends Animal {
@Override
public void makeSound() { System.out.println("喵"); }
public void catchMouse() { System.out.println("抓老鼠"); }
}
public class Main {
public static void main(String[] args) {
Animal a = new Cat();
a.makeSound(); // ✅ 多态生效:输出"喵"
// a.catchMouse(); // ❌ 编译错误:找不到符号
if (a instanceof Cat) {
((Cat) a).catchMouse(); // ✅ 安全调用
}
}
}
最易被忽略的一点:多态只对**实例方法**生效;static 方法、final 方法、构造器、字段访问,全部按引用类型(编译时类型)解析 —— 它们不参与动态绑定。








