一、多态是什么?(一句话核心)
多态 = 同一个动作,不同对象表现出不同结果 → 本质是 “父类引用指向子类对象”,调用统一方法时,自动执行子类的具体实现。
生活类比:
- 动作统一:“打招呼”;
- 不同对象:中国人 / 美国人 / 猫 / 狗;
- 不同结果:说 “你好”/ 说 “Hello”/ 喵喵叫 / 汪汪叫。
二、多态的 3 个必要条件(缺一不可)
表格
| 条件 | 大白话解释 | 代码示例 |
|---|---|---|
| 继承 / 实现 | 子类必须是父类的 “一种” | Cat extends Animal |
| 方法重写 | 子类定制父类的通用方法 | Cat重写Animal的shout() |
| 父类引用子类 | 用父类变量装子类对象 | Animal cat = new Cat(); |
三、核心语法:父类引用指向子类对象
1. 为什么能这么写?
子类继承父类后,子类对象天生具备父类的类型特征(猫是一种动物),就像 “大盒子能装小盒子”:
- ✅ 合法:
Animal cat = new Cat();(父类变量装子类对象); - ❌ 非法:
Cat c = new Animal();(子类变量不能装父类对象,苹果袋不能装随便一个水果)。
2. 关键特性
- 编译看左边(父类):变量只能调用父类中定义的方法(比如
Animal的shout()); - 运行看右边(子类):实际执行的是子类重写后的方法(比如
Cat的shout())。
四、无多态 vs 有多态:完整代码对比(核心补充)
场景:实现 “喂不同动物” 功能
1. 无多态实现(硬编码,扩展性差)
// 动物父类(仅定义,无多态调用)
public class Animal {
public void shout() {
System.out.println("动物发出声音");
}
}
// 猫子类
public class Cat extends Animal {
@Override
public void shout() {
System.out.println("猫-喵喵叫");
}
}
// 狗子类
public class Dog extends Animal {
@Override
public void shout() {
System.out.println("狗-汪汪叫");
}
}
// 调用方:喂动物(无多态,需为每个动物写专属方法)
public class Person {
// 喂猫:专属方法
public void feedCat(Cat cat) {
cat.shout();
}
// 喂狗:专属方法
public void feedDog(Dog dog) {
dog.shout();
}
// 新增动物?必须加新方法:feedBird()、feedFish()……
}
// 测试类
public class TestNoPolymorphism {
public static void main(String[] args) {
Person me = new Person();
// 喂猫:必须用Cat类型变量
Cat cat = new Cat();
me.feedCat(cat); // 输出:猫-喵喵叫
// 喂狗:必须用Dog类型变量
Dog dog = new Dog();
me.feedDog(dog); // 输出:狗-汪汪叫
// 新增喂鸟?
// 1. 先写Bird类
// 2. 再给Person加feedBird()方法
// 3. 测试类新增调用代码
}
}
2. 有多态实现(统一调用,扩展性强)
// 动物父类(同上)
public class Animal {
public void shout() {
System.out.println("动物发出声音");
}
}
// 猫子类(同上)
public class Cat extends Animal {
@Override
public void shout() {
System.out.println("猫-喵喵叫");
}
// 子类特有方法(父类变量无法直接调用)
public void scratch() {
System.out.println("猫-挠人");
}
}
// 狗子类(同上)
public class Dog extends Animal {
@Override
public void shout() {
System.out.println("狗-汪汪叫");
}
}
// 调用方:喂动物(有多态,仅需1个通用方法)
public class Person {
// 统一方法:不管喂啥动物,都用这一个方法
public void feed(Animal animal) {
animal.shout(); // 运行时自动执行子类的shout()
}
}
// 测试类
public class TestPolymorphism {
public static void main(String[] args) {
Person me = new Person();
// 喂猫:父类引用装猫对象
Animal cat = new Cat();
me.feed(cat); // 输出:猫-喵喵叫
// 喂狗:父类引用装狗对象
Animal dog = new Dog();
me.feed(dog); // 输出:狗-汪汪叫
// 新增喂鸟?仅需2步:
// 1. 写Bird类(重写shout())
// 2. 直接调用:me.feed(new Bird());
// Person类一行代码都不用改!
}
}
3. 无多态 vs 有多态:核心差异表
表格
| 维度 | 无多态实现 | 有多态实现 |
|---|---|---|
| 方法数量 | 每个子类对应 1 个专属方法(feedCat/feedDog) | 仅 1 个通用方法(feed (Animal)) |
| 扩展成本 | 新增子类必须修改调用方(加新方法) | 新增子类无需修改调用方(仅加子类) |
| 代码冗余 | 高(重复写相似逻辑) | 低(逻辑统一,仅子类定制实现) |
| 耦合程度 | 高(调用方依赖具体子类) | 低(调用方依赖抽象父类) |
| 维护成本 | 高(改一处要改多个方法) | 低(改父类逻辑,子类自动适配) |
五、多态的核心价值(为什么要用?)
1. 调用更简单:少记方法名
- ❌ 无多态:要写
feedCat()、feedDog()、feedBird()…… - ✅ 有多态:只写
feed(Animal animal),传啥对象都能适配。
2. 扩展更方便:符合 “开闭原则”
新增Bird类时,只需:
public class Bird extends Animal {
@Override
public void shout() {
System.out.println("鸟-叽叽叫");
}
}
调用方Person的feed()方法一行都不用改,直接调用me.feed(new Bird())即可。
3. 耦合更低:依赖抽象而非具体
调用方只关心 “动物会叫”(父类抽象),不关心 “猫怎么叫、狗怎么叫”(子类具体),代码更稳定。
六、常见误区
- ❌ 多态能调用子类特有方法?不能!
Animal cat = new Cat();中cat无法直接调用scratch(),需向下转型:((Cat)cat).scratch()(不推荐频繁用)。 - ❌ 多态适用于属性?不适用于!属性编译和运行都看左边(父类),多态只针对方法。
七、无多态 vs 有多态 核心总结
1. 多态的核心
父类引用指向子类对象,调用统一方法,执行子类实现;
2. 无多态 vs 有多态 核心差异
- 无多态:“一对一”(一个子类对应一个调用方法),扩展 / 维护成本高;
- 有多态:“一对多”(一个通用方法适配所有子类),扩展 / 维护成本低;
3. 多态的本质
面向抽象编程,不关心具体实现,只关心 “能做什么”(动物会叫),而非 “怎么做”(猫喵喵叫 / 狗汪汪叫)。
最后一句大白话:
- 无多态:我要喂猫就写喂猫的代码,喂狗就写喂狗的代码,新增动物就得重写代码;
- 有多态:我只写 “喂动物” 的代码,不管是猫是狗,它们自己知道该怎么吃 / 怎么叫,新增动物直接塞进去就行。
总结
- 无多态实现是 “硬编码适配”,每个子类对应专属调用方法,扩展和维护成本极高;有多态是 “抽象适配”,仅需 1 个通用方法适配所有子类,符合开闭原则。
- 多态的核心价值是降低耦合、简化调用、提升扩展性,本质是 “面向抽象编程” 而非 “面向具体实现编程”。
- 多态的 3 个必要条件(继承 / 实现、方法重写、父类引用子类)缺一不可,且仅针对方法生效(属性无多态)。