静态多态靠编译期绑定,仅通过方法重载实现,由参数类型、数量、顺序决定调用目标;动态多态依赖运行时类型,通过方法重写和虚方法表实现,要求非private/final/static实例方法、向上转型引用及子类实例。

静态多态靠编译期绑定,本质是方法重载
Java 中的静态多态只有一种实现方式:**方法重载(overloading)**。它发生在编译阶段,由编译器根据**参数类型、数量、顺序**决定调用哪个方法,和对象实际类型无关。javac 会直接把方法调用解析成具体的方法符号引用,生成的字节码里就是 invokestatic 或 invokevirtual 指向那个确定的签名。
常见误区是把 final 方法或 private 方法当成静态多态——它们确实不能被重写,但本身不构成“多态”,只是无法参与动态分派而已。
- 重载方法必须在同一个类中,或子类中定义(但仍是独立方法,不覆盖父类)
- 仅返回类型不同不算重载,编译报错:
method xxx() is already defined - 自动装箱/拆箱、可变参数可能引发意外匹配,比如传
null给多个重载方法时,编译器可能报错或选错目标
动态多态依赖运行时类型,核心是方法重写
动态多态对应的是**方法重写(overriding)**,它依赖 JVM 的虚方法调用机制。编译期只检查方法是否可访问、签名是否匹配;真正调用哪个版本,由对象**运行时的实际类型**决定,通过虚方法表(vtable)查找完成。
这是 Java 实现面向对象“多态性”的主干路径,也是 interface、abstract class 和多态容器(如 List)能工作的基础。
立即学习“Java免费学习笔记(深入)”;
- 重写要求方法名、参数列表、返回类型(协变允许子类型)一致,且访问权限不能更严格
-
static方法可以“同名同参”出现在父子类中,但这不是重写,而是隐藏(hiding),调用取决于**引用类型**,不是实际类型 -
final、private、static方法无法被重写,因此不参与动态分派
看字节码最清楚:invokestatic vs invokevirtual
静态多态的方法调用,在字节码中通常是 invokestatic(对静态方法)或明确指向某个重载版本的 invokevirtual;而动态多态一定使用 invokevirtual(实例方法)或 invokeinterface(接口方法),且目标方法在字节码里只写签名,不写具体实现类。
你可以用 javap -c 对比以下两个例子:
public class Test {
void foo(String s) { System.out.println("String"); }
void foo(Object o) { System.out.println("Object"); }
public static void main(String[] args) {
Test t = new Test();
t.foo("hello"); // 编译期就选定 foo(String)
}
}
class Animal { void speak() { System.out.println("Animal"); } }
class Dog extends Animal { void speak() { System.out.println("Woof"); } }
public class Main {
public static void main(String[] args) {
Animal a = new Dog();
a.speak(); // 字节码是 invokevirtual Animal.speak,但运行时执行 Dog.speak
}
}
容易被忽略的关键点:泛型擦除不影响动态多态
Java 泛型在运行时被擦除,但动态多态不受影响。比如 List 和 List 在运行时都是 List,但如果你重写了 ArrayList.add(),它的动态分派照常工作——因为擦除的是类型参数,不是对象的实际类层级。
真正容易出错的是把泛型当作运行时类型来判断,比如误以为 if (list instanceof List 有效(它永远为 false),或者期望泛型约束参与重载解析(编译器根本不认 做重载区分)。
动态多态生效的前提始终只有一个:**非 private / final / static 的实例方法 + 向上转型引用 + 运行时子类实例**。其余都是干扰项。










