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("測試人員正在尋找錯誤。");
}
}
以基底型別的陣列/列表來使用
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(); // 會針對每個物件呼叫「正確」的方法版本
}
}
}
執行結果:
經理正在開會。
開發者正在寫程式碼。
測試人員正在尋找錯誤。
開發者正在寫程式碼。
有什麼好處?
- 你不用寫一堆像「如果是 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("測試人員正在尋找錯誤。");
}
}
步驟 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