1. 集合中的多态:为什么需要它?
先从一个问题开始:“在真实的程序里,为什么需要多态?”
想象一下动物园。你有一个基类 Animal,以及一堆子类:Dog、Cat、Cow、Parrot,甚至还有 Platypus(鸭嘴兽,给喜爱异域风情的人)。它们都会发出声音(makeSound()),但实现各不相同。
与其为每种动物创建单独的数组,不如声明一个 Animal 类型的数组或列表,把谁都可以放进去:
Animal[] animals = {
new Dog(),
new Cat(),
new Cow(),
new Parrot()
};
现在你可以遍历这个数组,并为每个元素调用 makeSound():
for (Animal animal : animals) {
animal.makeSound();
}
魔法!每个对象都知道该发出什么声音,你无需写一堆 if 或 switch。
类比示例
就好像你对一群动物下达“发声!”的命令,每个动物都会自己决定怎么做:狗会汪汪叫,猫会喵喵叫,而牛会哞哞叫。你不需要确认它们是谁——只需调用同一个方法。
2. 实战示例:员工层次结构
来一个更贴近现实(也贴近你未来的 IT 工作)的例子。假设公司里有不同的员工:经理、开发、测试。每个人都有 work() 方法,但实现不同。
声明基类
public class Employee {
public void work() {
System.out.println("员工在工作...");
}
}
子类
public class Manager extends Employee {
@Override
public void work() {
System.out.println("经理正在开会。");
}
}
public class Developer extends Employee {
@Override
public void work() {
System.out.println("开发人员在写代码。");
}
}
public class Tester extends Employee {
@Override
public void work() {
System.out.println("测试人员在查找 bug。");
}
}
使用基类型的数组/列表
public class CompanyDemo {
public static void main(String[] args) {
Employee[] team = {
new Manager(),
new Developer(),
new Tester(),
new Developer()
};
for (Employee e : team) {
e.work(); // 对每个对象都会调用 "正确" 的方法版本
}
}
}
运行结果:
经理正在开会。
开发人员在写代码。
测试人员在查找 bug。
开发人员在写代码。
有什么好处?
- 不用写大量类似“如果是 Developer — 就做某某”的分支检查。
- 要添加一种新员工(例如 Designer)— 只需创建一个新类并把它放进数组即可。
- 使用员工数组的那部分代码完全不用改!
3. 多态的优势:灵活性与可扩展性
假设你的公司新增了一种员工类型 — Designer。你需要做的只是创建一个新类:
public class Designer extends Employee {
@Override
public void work() {
System.out.println("设计师在绘制原型。");
}
}
现在把设计师加入团队:
Employee[] team = {
new Manager(),
new Developer(),
new Tester(),
new Designer()
};
搞定!程序会立刻正确处理这个新员工类型,而无需改动那段遍历数组并调用 work() 的代码一行。
这就是可扩展性:你的代码可以轻松适配新的对象类型。
4. 多态的限制:硬币的另一面
遗憾的是,任何“魔法”都有其限制(就像 RPG 里一样也要付出代价)。
只能访问基类中声明的方法
当你通过 Employee 类型的变量工作时,只能调用在 Employee 类中声明的方法。如果 Developer 有一个专有方法 writeCode(),你不能直接调用它:
Employee e = new Developer();
// e.writeCode(); // 编译错误:Employee 中没有这个方法!
如果你非常想调用这个特有方法,就得进行类型转换。但这只是下下策。如果你经常需要做类型转换,可能应当重新审视类的设计——基类或接口应包含所需的方法。
if (e instanceof Developer) {
Developer dev = (Developer) e;
dev.writeCode();
}
但这样就失去了本应有的通用性与优雅。因此,尽量把基类设计为只包含所有子类都真正需要的方法。
5. 动手实践:实现员工层次结构
来做个小应用:包含若干种员工类型,并用多态对其进行处理。
步骤 1:基类与子类
// Employee.java
public class Employee {
public void work() {
System.out.println("员工在工作...");
}
}
// Manager.java
public class Manager extends Employee {
@Override
public void work() {
System.out.println("经理正在开会。");
}
}
// Developer.java
public class Developer extends Employee {
@Override
public void work() {
System.out.println("开发人员在写代码。");
}
}
// Tester.java
public class Tester extends Employee {
@Override
public void work() {
System.out.println("测试人员在查找 bug。");
}
}
步骤 2:主类
// CompanyDemo.java
public class CompanyDemo {
public static void main(String[] args) {
Employee[] team = {
new Manager(),
new Developer(),
new Tester(),
new Developer()
};
for (Employee e : team) {
e.work();
}
}
}
步骤 3:加入可扩展性
假设一个月后公司来了新同事——设计师。你需要做的只是:
public class Designer extends Employee {
@Override
public void work() {
System.out.println("设计师在绘制原型。");
}
}
好了,现在把设计师加入团队:
Employee[] team = {
new Manager(),
new Developer(),
new Tester(),
new Designer()
};
小结
主体代码(CompanyDemo)保持不变!这就是多态的力量。
6. 使用多态时的常见错误
错误 1:期望通过基类引用访问子类的特有方法。
初学者常会尝试通过超类类型的变量调用子类的特有方法。例如:
Employee e = new Developer();
// e.writeCode(); // 错误!Employee 中没有定义该方法。
要调用特有方法必须进行类型转换,但这会损失通用性。
错误 2:未使用注解 @Override。
如果忘记该注解,可能会因为命名拼写等问题写出一个新方法而不是重写。这样一来多态就不会生效,将会调用父类版本。
错误 3:缺少共同的接口。
如果基类没有包含所需的方法,多态就无从谈起。比如,如果在 Employee 中没有 work() 方法,那么遍历员工数组的循环就无法为所有对象调用该方法。
错误 4:违反开闭原则。
如果为了添加新的员工类型不得不修改遍历数组/列表的代码——说明你没有正确地使用多态。
GO TO FULL VERSION