多态是运行时动态决定调用哪个子类方法的能力,需同时满足父类引用指向子类对象、子类重写父类方法、向上转型三个条件,缺一不可;接口多态与继承多态机制相同,本质均为运行期根据实际对象类型查方法表实现动态绑定。

多态不是“写法花样”,而是运行时动态决定调用哪个子类方法的能力——它只在 父类引用指向子类对象 + 子类重写父类方法 时生效,其他情况全是假多态。
多态只在运行时发生,编译期根本不知道具体类型
Java 编译器只检查 Animal 类里有没有 makeSound() 方法;真正执行哪段逻辑,得等程序跑起来,看 animal 实际是 Dog 还是 Cat 对象。这就是所谓“晚绑定”(dynamic binding)。
- 编译时:只认引用类型(如
Animal animal),不关心new Dog() - 运行时:JVM 查对象的虚函数表(vtable),跳转到子类重写后的地址
- 如果没重写,就走父类默认实现——这不是多态,只是继承调用
三个条件缺一不可,漏一个就退化成普通调用
常见错误是只做了继承或只写了重写,却忘了最关键的一步:用父类类型声明变量。
- ✅ 正确:
Animal animal = new Dog(); animal.makeSound();→ 输出“汪汪汪” - ❌ 错误1:
Dog dog = new Dog(); dog.makeSound();→ 看似一样,但这是静态调用,无多态 - ❌ 错误2:
Animal animal = new Animal(); animal.makeSound();→ 调用父类方法,没子类参与,不算多态 - ❌ 错误3:子类写了同名方法但没加
@Override,且参数稍有不同(比如多了一个int volume)→ 实际是重载,不是重写,多态失效
接口多态和继承多态本质相同,只是语法起点不同
用接口也能实现完全一样的动态行为,底层机制一致:JVM 根据实际对象类型查实现类的方法表。
立即学习“Java免费学习笔记(深入)”;
interface Payment {
void pay(double amount);
}
class WeChatPay implements Payment {
public void pay(double amount) {
System.out.println("微信扫码支付 " + amount);
}
}
class Alipay implements Payment {
public void pay(double amount) {
System.out.println("支付宝指纹支付 " + amount);
}
}
// 多态调用
Payment p = new WeChatPay();
p.pay(99.9); // 输出微信逻辑
p = new Alipay();
p.pay(99.9); // 输出支付宝逻辑
注意:接口没有构造器、不能实例化,所以必须靠实现类对象来承载行为——这点和抽象类多态一样,只是设计意图更侧重“能力契约”。
别把方法重载(Overload)当成多态
add(int, int) 和 add(double, double) 是编译时确定的,属于静态多态,和面向对象语义里的“多态”无关。面试或设计中混淆这两者,会暴露对 OOP 基础理解偏差。
- 重载:同一个类内,方法名相同、参数列表不同 → 编译期绑定
- 重写:父子类间,方法签名完全一致 → 运行期绑定
- 真多态只指后者,且必须配合向上转型才体现价值
最容易被忽略的是:多态的价值不在“能写出不同输出”,而在于让调用方(比如 playSound(Animal a) 方法)完全不用改代码,就能接纳未来任意新增的动物子类——这才是开闭原则落地的关键切口。










