编译时多态即方法重载,由编译器根据实参类型、个数、顺序静态绑定;运行时多态依赖继承、重写和向上转型,由jvm基于对象实际类型动态分派。

编译时多态就是方法重载,靠参数签名静态绑定
Java里没有真正意义上的“编译时多态”这个语言特性,所谓编译时多态,只是对方法重载(overload)行为的通俗称呼。它本质是编译器在编译阶段就根据调用处的实参类型、个数、顺序,从多个同名方法中选出唯一匹配的版本,并固化到字节码里——后续运行时不会再换。
常见错误现象:add(1, 2) 调用的是 int add(int, int),但若把参数改成 add(1L, 2) 却报错“找不到匹配方法”,不是因为逻辑错了,而是编译器根本没找到 add(long, int) 这个重载签名。
- 重载只看声明类型:变量
Object o = new String("a"),调用print(o)会走print(Object),哪怕实际是String也绝不触发print(String) - 返回值、异常、修饰符(
static/final)不影响重载判定 - 子类可以重载父类方法,但这是两个独立方法,不构成继承关系下的行为替换
运行时多态才是真多态,靠对象实际类型动态分派
运行时多态才是 Java 多态的核心,它依赖继承 + 方法重写(override)+ 向上转型,由 JVM 在运行时通过虚方法表(vtable)查表决定最终执行哪个版本。
使用场景典型如:Animal a = new Dog(); a.sound();——编译时只认 Animal.sound() 签名是否合法,运行时才查 Dog 类有没有重写,有就调它的,没有就沿继承链往上找。
立即学习“Java免费学习笔记(深入)”;
- 必须是非
private、非static、非final的实例方法;static方法看似能“重写”,实则是隐藏(hiding),属于编译时绑定 - 调用目标完全取决于
new出来的右边对象类型,和左边引用类型无关 - 接口实现(
Shape s = new Circle())和抽象类子类化,机制一致,都走动态绑定
为什么重载不能替代重写?关键在绑定时机
一个简单判断法:删掉某个子类,如果代码直接编译失败(比如找不到某个重载方法),那就是编译时行为;如果仍能编译通过,但运行结果随对象不同而变化,才是运行时多态。
容易踩的坑:public void feed(Animal a) 和 public void feed(Dog d) 是重载,不是重写。当你传入 new Dog(),编译器按引用类型选方法——如果变量是 Animal a = new Dog(),就会进 feed(Animal),永远进不了 feed(Dog),哪怕对象真是 Dog。
- 重载是“静态分派”,基于编译期已知的参数类型做决策
- 重写是“动态分派”,依赖运行期对象的实际 class 元数据
- 二者共存时,先解决重载(选哪个方法签名),再解决重写(该签名下执行谁的实现)
final/static/private 方法无法参与运行时多态
这些修饰符会阻止 JVM 建立虚方法调用路径:private 方法不可见,static 方法属于类而非实例,final 方法禁止覆盖。它们统统被编译器内联或直接绑定到具体类,失去动态性。
性能影响其实微乎其微,现代 JVM 对虚调用优化极强(如去虚拟化、内联缓存),但语义上一旦加了这些修饰符,你就主动放弃了多态能力。
-
@Override注解只能用于可重写的方法,加在static或private上会编译报错 - 子类里定义同签名
static方法,只是隐藏父类方法,调用结果取决于引用类型,不是运行时类型 - 想让某个方法支持多态又限制子类修改逻辑?用
final修饰方法体,但别封死整个方法







