java中多态调用发生在运行时,通过invokevirtual指令结合虚方法表动态分派;需满足父类引用指向子类实例、方法非static/final/private、子类正确重写三个条件。

多态调用实际发生在哪里?
Java 中的多态调用不是编译期决定的,而是在运行时通过 invokevirtual 指令 + 虚方法表(vtable)动态分派的。你写的 animal.speak() 看似调用父类方法,但 JVM 会查 animal 实际指向的对象类型(比如 Dog),再找该类型的 speak() 入口地址。
常见错误现象:NullPointerException 却报在 speak() 调用行 —— 其实是 animal 为 null,还没走到多态分派那步;真正多态失败的表现是“调了父类方法,没进子类重写体”,通常是忘了 override 或访问权限不对。
- 必须满足:父类引用指向子类实例(
Animal animal = new Dog();) - 被调用方法必须是非
static、非final、非private - 子类中必须用
@Override显式标注(非强制但强烈建议,避免签名不一致)
为什么子类方法没被调到?检查这三处
最常踩的坑不是语法错,而是语义断点:编译通过,运行却走父类逻辑。本质是 JVM 没识别出这是可覆盖的方法调用。
-
static方法不能多态:写成Animal.speak()是静态绑定,和对象实例无关;哪怕子类也定义了同名static方法,也不会被多态选中 - 构造器里调用可重写方法:子类构造未完成时,
this已指向子类实例,但子类字段可能还是默认值,此时调speak()会进子类方法体,但读到的是未初始化字段 - 方法签名不一致:子类写了
speak(String msg),父类是speak(),这不是重写,是重载 —— 多态根本不会触发
多态和 instanceof / getClass() 的关系
多态本身不关心“是什么类型”,只关心“能响应什么消息”。但人写代码时常需要类型判断,这就容易混淆运行时类型和编译时类型。
立即学习“Java免费学习笔记(深入)”;
-
animal instanceof Dog返回true,说明运行时对象确实是Dog实例 —— 这和多态能否发生无关,只是类型断言 -
animal.getClass() == Dog.class和instanceof行为不同:前者严格相等,后者支持继承链匹配 - 滥用
instanceof后强转再调方法(if (a instanceof Dog) ((Dog)a).bark()),就绕过了多态本意,也破坏了开闭原则
字节码层面看一次多态调用
用 javap -c 看编译后的字节码,关键线索是 invokevirtual 指令 —— 它后面跟的是变量声明类型(如 Animal.speak),而不是实际类型。JVM 在运行时才根据栈顶对象的实际类去 vtable 查具体实现。
示例片段:
aload_1 invokevirtual Animal.speak:()V
注意:invokevirtual 不是 invokestatic 或 invokespecial;如果看到后者,说明这次调用根本没走多态流程。
真正复杂的地方在于:这个机制对开发者透明,但一旦涉及代理(如 Spring AOP)、模块化(JPMS)、或反射调用,vtable 查找路径可能被拦截或绕过 —— 那时候 speak() 看似被重写了,却没生效,得往字节码增强或类加载器方向查。









