1. 抽象類別與方法
有時在生活(也在程式設計)中,我們會說:「嗯,我不知道它究竟怎麼做,但我確信它應該存在!」例如,所有動物都應該會發出聲音,但發出什麼聲音——取決於具體的動物。針對這類情況,Java 設計了 抽象類別 與 抽象方法。
抽象類別 是無法直接被建立的類別(如果 Animal 是抽象的,不能寫 new Animal()),但可以被繼承。這樣的類別可以同時包含一般(已實作)的方法與抽象的方法——也就是已宣告但尚未實作的方法。
抽象方法 是沒有方法本體的方法。它以關鍵字 abstract 宣告,且必須在子類別中實作(除非子類別本身也是抽象的)。
生活化範例
假設我們有一個動物園的應用程式。我們希望所有動物都有一個 makeSound() 方法,但不知道它們各自會發出什麼聲音。那我們就建立一個抽象類別:
public abstract class Animal {
public abstract void makeSound(); // 抽象方法
}
具體的動物各自以自己的方式實作這個方法:
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪!");
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("喵!");
}
}
現在,如果有人試圖建立 new Animal(),編譯器會立刻說:「抱歉,抽象的動物在自然界並不存在!」這很有用:你能保證程式中只會存在具有具體行為的具體動物。
2. 透過抽象實現多型
抽象 是為一組物件抽取出共同介面的過程。抽象類別正是用來定義這個共同介面:它指出所有子類別必須實作哪些方法。
多型與抽象相輔相成:抽象類別保證所有繼承者都有需要的方法,而多型允許你透過基底型別的參考來呼叫這些方法。
範例:建立一座動物園
讓我們組一個小型動物園。我們有抽象類別 Animal 以及它的幾個子類別:
public abstract class Animal {
public abstract void makeSound();
}
public class Cow extends Animal {
@Override
public void makeSound() {
System.out.println("哞——!");
}
}
public class Duck extends Animal {
@Override
public void makeSound() {
System.out.println("呱呱!");
}
}
現在我們可以建立一個動物陣列:
Animal[] zoo = {
new Dog(),
new Cat(),
new Cow(),
new Duck()
};
for (Animal animal : zoo) {
animal.makeSound(); // 對每個動物都會呼叫到「正確」的方法
}
陣列中的每個物件都是具體的動物,但對程式碼而言它只是 Animal。仰賴多型與抽象,我們可以確信每個物件都有 makeSound() 方法,而且會正確運作。
3. 使用抽象類別來實現多型
讓我們看一個更實用的例子。假設我們在開發公司員工管理的應用程式。我們有不同類型的員工:經理、開發者、測試人員。大家都有一個共同的方法 work(),但執行方式各不相同。
抽象類別 Employee
public abstract class Employee {
protected String name;
public Employee(String name) {
this.name = name;
}
public abstract void work();
}
具體子類別
public class Manager extends Employee {
public Manager(String name) {
super(name);
}
@Override
public void work() {
System.out.println(name + " 帶領團隊。");
}
}
public class Developer extends Employee {
public Developer(String name) {
super(name);
}
@Override
public void work() {
System.out.println(name + " 撰寫程式碼。");
}
}
public class Tester extends Employee {
public Tester(String name) {
super(name);
}
@Override
public void work() {
System.out.println(name + " 測試應用程式。");
}
}
多型的使用
現在我們可以建立一個員工陣列,並為每個員工呼叫 work() 方法:
Employee[] employees = {
new Manager("Anna"),
new Developer("Ivan"),
new Tester("Mariya")
};
for (Employee e : employees) {
e.work();
}
輸出:
Anna 帶領團隊。
Ivan 撰寫程式碼。
Mariya 測試應用程式。
請注意:在迴圈中我們不知道(也不想知道!)具體的員工型別。我們只呼叫 work(),每個物件就會各司其職。
4. 實用細節
方法實作的保證
抽象類別會強制所有繼承者實作所需的方法。如果你忘了在子類別中實作抽象方法,編譯器立刻會提醒你:「你必須這麼做!」
通用介面
處理抽象型別的陣列或清單(Employee[]、List<Animal>)的程式碼可以完全通用。你可以新增新的子類別——而主要程式碼毋須修改。
防止「不合規」的物件
由於抽象類別無法直接建立,沒有人會不小心做出一個「不明所以」且未實作必要方法的物件。
理論與語法:如何宣告抽象類別與方法
- 抽象類別以關鍵字 abstract 置於 class 前宣告。
- 抽象方法以關鍵字 abstract 宣告,且沒有方法本體(只以分號結束)。
- 只要類別內有至少一個抽象方法,該類別就必須是抽象的。
- 繼承抽象類別的類別必須實作其所有抽象方法,否則它本身也必須是抽象的。
範例結構
public abstract class Animal {
public abstract void makeSound();
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("喵!");
}
}
5. 使用抽象類別時的常見錯誤
錯誤 1:嘗試建立抽象類別的物件。
像 new Animal() 這樣的程式碼無法編譯。抽象類別就像只有組裝說明、沒有零件的家具:在出現具體子類別之前,無法組出物件。
錯誤 2:忘了在子類別實作抽象方法。
如果你宣告了抽象方法,卻沒有在繼承者中實作它(也未將該類別標記為抽象),編譯器會不高興地報錯。
錯誤 3:忽略了存取修飾子。
被覆寫的方法不能有比基底類別更嚴格的存取修飾子。舉例來說,如果抽象方法是 public,那實作也必須是 public(而不是 protected 或 private)。
錯誤 4:試圖讓抽象方法帶有方法本體。
abstract 方法不能有方法本體,否則編譯器會翻白眼說:「請你下定決心——要嘛抽象,要嘛實作!」
錯誤 5:多型不適用於靜態方法。
多型只對非靜態方法有效。靜態方法不會被覆寫——它們是被隱藏,因此呼叫時的行為取決於變數的型別,而非實際物件。
GO TO FULL VERSION