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