1. 方法覆寫
在現實中常見「子代」有特別行為的情況。比如說,所有動物都會發出聲音,但貓會「喵」、狗會「汪」,而程式設計師會「哎呀,又是 bug!」。在程式設計中,這是透過方法覆寫(override)來實現的。
方法覆寫是指子類別為父類別中已宣告的方法提供自己的實作。也就是說,用更具體的行為來「替換」預設行為。
類比:如果把父類別想成是羅宋湯的招牌食譜,那麼方法覆寫就像奶奶在裡面加入她的祕密配方。它仍然是羅宋湯,但每個人的風味都不同。
要覆寫方法,必須在子類別中宣告與父類別完全相同簽名(名稱、參數、回傳型別)的方法。
範例:動物與它們的聲音
class Animal {
void makeSound() {
System.out.println("Some generic animal sound");
}
}
class Dog extends Animal {
// 覆寫 makeSound() 方法
void makeSound() {
System.out.println("Woof!");
}
}
class Cat extends Animal {
// 覆寫 makeSound() 方法
void makeSound() {
System.out.println("Meow!");
}
}
現在,如果建立一個 Dog 物件並呼叫 makeSound(),你會聽到 "Woof!",而不是 "Some generic animal sound"。
程式碼示範
public class Main {
public static void main(String[] args) {
Animal generic = new Animal();
Dog dog = new Dog();
Cat cat = new Cat();
generic.makeSound(); // Some generic animal sound
dog.makeSound(); // Woof!
cat.makeSound(); // Meow!
}
}
重要:如果子類沒有提供相同簽名的方法,將會使用父類的方法。
2. 註解 @Override:用途與使用方式
在 Java 中,慣例會用 @Override 來標示被覆寫的方法。這不只是程式碼的裝飾,而是很有用的工具:
- 編譯器會檢查你是否真的覆寫了父類的方法。如果你在名稱、參數型別或回傳型別上打錯,編譯器會報錯。
- 提升程式碼可讀性。其他程式設計師一看就知道:「哦,這個方法覆寫了父類的方法」。
搭配 @Override 的範例
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Woof!");
}
}
如果不小心把 void makeSound() 寫成 void makeSond()(打字錯誤!),而方法上又標了 @Override,編譯器會抱怨:"Method does not override or implement a method from a supertype"。
現代標準:使用 @Override 是良好習慣與業界標準。即使編譯器不強制,仍建議一律加上這個註解——對你自己與同事都更友善。
3. 覆寫方法的呼叫如何運作
當你在子類物件上呼叫方法時,即使變數宣告為父類型別,也會使用子類的實作。
範例:多型的實際運作
Animal animal = new Dog();
animal.makeSound(); // "Woof!", 而不是 "Some generic animal sound"
這裡變數型別是 Animal,但實際上存放的是 Dog 物件。Java 會「知道」要呼叫 Dog 中被覆寫的方法。這就是多型(更多內容將在後續講解)。
4. 覆寫的限制與規則
方法簽名
- 名稱、參數型別與順序必須與父類的方法相同。
- 回傳型別必須相同,或是協變(為父類回傳型別的子型別)。例如,若父類回傳 Animal,子類回傳 Dog 是允許的。
存取修飾子
- 不能把可見度縮得比父類還嚴格。
- 如果父類方法是 public,覆寫後的方法也必須是 public。
- 如果父類是 protected,覆寫後可以是 protected 或 public。
若嘗試反其道而行,編譯器會說:"Cannot reduce the visibility of the inherited method"。
例外
- 覆寫的方法不得拋出父類宣告中不存在的新的受檢例外(checked exception)。
- 可以拋出更少的例外,或是父類例外的子型別。
static、final、private
- 無法覆寫宣告為 static 或 final 的方法,以及私有的(private)方法。
- static 是隱藏(hiding),不是覆寫。
- final 不能被覆寫,Java 會保護這類方法。
- private 在子類中不可見,不能覆寫(只能宣告同名的新方法)。
建構子
建構子不會被繼承,也無法覆寫。每個類別都有自己的建構子。
5. 擴充教學應用程式「動物園」
是時候把理論用在實作上了!讓我們繼續擴充「動物園」應用程式。
步驟 1. 基底類別 Animal
public class Animal {
public void makeSound() {
System.out.println("Some generic animal sound");
}
public void sleep() {
System.out.println("Zzz...");
}
}
步驟 2. 子類別 Dog 與 Cat
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Woof!");
}
// 只有 Dog 才有的額外方法
public void fetch() {
System.out.println("Dog brings the stick!");
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Meow!");
}
// 只有 Cat 才有的額外方法
public void scratch() {
System.out.println("Cat scratches the sofa!");
}
}
步驟 3. 使用覆寫
public class ZooTest {
public static void main(String[] args) {
Animal generic = new Animal();
Animal dog = new Dog();
Animal cat = new Cat();
generic.makeSound(); // Some generic animal sound
dog.makeSound(); // Woof!
cat.makeSound(); // Meow!
// dog.fetch(); // 錯誤!型別為 Animal 的變數不知道 fetch()
// cat.scratch(); // 同理
// 但如果明確指定型別:
if (dog instanceof Dog) {
((Dog) dog).fetch(); // Dog brings the stick!
}
if (cat instanceof Cat) {
((Cat) cat).scratch(); // Cat scratches the sofa!
}
}
}
說明:
makeSound() 方法具有多型行為——會呼叫物件實際類別中的版本。而特定於子類的功能(fetch、scratch)只有在明確轉型後才可使用——這對理解繼承與覆寫的運作很重要。
6. 回傳型別範例(協變)
有時候會希望覆寫的方法回傳更「狹窄」的型別。例如:
class Animal {
Animal getFriend() {
return new Animal();
}
}
class Dog extends Animal {
@Override
Dog getFriend() { // 回傳型別是 Dog,為 Animal 的子型別
return new Dog();
}
}
這稱為回傳型別的協變,在 Java(自 Java 5 起)是被允許的。
7. 如果不使用 @Override 會怎樣?
如果你在方法名稱或參數上不小心出錯,而又沒有加上 @Override 註解,Java 並不會報錯。結果就不是覆寫,而是定義了一個新方法,預期的行為也不會改變。
錯誤範例
class Dog extends Animal {
// 打字錯誤:把 makeSound 寫成 makeSoud
void makeSoud() {
System.out.println("Woof!");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
dog.makeSound(); // 會輸出 "Some generic animal sound"
}
}
如果有加上 @Override,編譯器就會報錯:"Method does not override or implement a method from a supertype"。
8. 覆寫方法的常見錯誤
錯誤 1:缺少 @Override 註解。
沒有它很容易在方法名稱或參數上出錯。結果方法不會被覆寫,程式行為也會與預期不同。
錯誤 2:嘗試縮小存取修飾子。
如果父類方法是 public,而你寫成 protected 或 private——就會得到編譯錯誤。
錯誤 3:簽名不一致。
如果參數哪怕只在型別上不同——那就不是覆寫,而是多載(overloading)。
錯誤 4:嘗試覆寫 final 或 static 方法。
Java 不允許這樣做:final 防止被覆寫,而 static 方法根本不會被覆寫(只能被隱藏)。
錯誤 5:將回傳型別改成不相容的型別。
只能回傳父類回傳型別的子型別(協變),不能換成完全不同的型別。
GO TO FULL VERSION