1. 介紹
Java 是一種廣泛應用於各個領域的程式語言,從企業系統到 Web 應用程式以及 Android 開發皆可見其身影。在眾多特性之中,「繼承」是學習物件導向程式設計時最重要的概念之一。
透過繼承,新的類別(子類別/子類)可以承接既有類別(父類別/超類別)的功能。這有助於減少程式碼重複,並使程式更容易擴充與維護。在 Java 中,繼承是以 extends 關鍵字實作的。
本文將清楚說明 extends 關鍵字在 Java 中的角色、基本用法、實務應用與常見問題。此指南不僅適合 Java 初學者,也適合想要複習繼承概念的開發者。閱讀完畢後,您將完整了解繼承的優缺點以及重要的設計考量。
現在就讓我們深入探討「Java 中的繼承是什麼?」吧。
2. 什麼是 Java 繼承?
Java 繼承是一種機制,讓一個類別(超類別/父類別)將其特性與功能傳遞給另一個類別(子類別/子類)。透過繼承,父類別中定義的欄位(變數)與方法(函式)可以在子類別中重複使用。此機制有助於更容易地組織與管理程式碼、集中共用流程,並彈性地擴充或修改功能。繼承是物件導向程式設計(OOP)三大核心支柱之一,另外兩個是封裝與多型。
關於「is-a」關係
繼承常見的例子是「is-a」關係。例如,「狗是一種動物」。這表示 Dog 類別繼承自 Animal 類別。狗可以擁有動物的特性與行為,同時加入自己的獨特功能。
class Animal {
void eat() {
System.out.println("食べる");
}
}
class Dog extends Animal {
void bark() {
System.out.println("ワンワン");
}
}
在此範例中,Dog 類別繼承自 Animal 類別。Dog 的實例可以同時使用 bark 方法以及繼承自 Animal 的 eat 方法。
使用繼承時會發生什麼?
- 您可以將共用的邏輯與資料集中於父類別,減少在每個子類別中重複撰寫相同程式碼的需求。
- 每個子類別可以加入自己的獨特行為,或覆寫父類別的方法。
使用繼承有助於整理程式結構,讓功能的新增與維護更為簡易。然而,繼承並非永遠是最佳選擇,設計時必須謹慎評估是否真的存在「is-a」關係。
3. extends 關鍵字的運作方式
extends 關鍵字在 Java 中用來明確宣告類別的繼承關係。當子類別繼承父類別的功能時,會在類別宣告中使用 extends ParentClassName 語法。這使得子類別能直接使用父類別的所有 public 成員(欄位與方法)。
基本語法
class ParentClass {
// Fields and methods of the parent class
}
class ChildClass extends ParentClass {
// Fields and methods unique to the child class
}
例如,使用前面的 Animal 與 Dog 類別,我們可以得到:
class Animal {
void eat() {
System.out.println("食べる");
}
}
class Dog extends Animal {
void bark() {
System.out.println("ワンワン");
}
}
透過 Dog extends Animal,Dog 類別繼承自 Animal,因此可以使用 eat 方法。
使用父類別成員
有了繼承,子類別的實例即可存取父類別的方法與欄位(只要存取修飾子允許):
Dog dog = new Dog();
dog.eat(); // Calls the parent class method
dog.bark(); // Calls the child class method
重要注意事項
- Java 只允許單一繼承(一次只能繼承一個類別)。在
extends後不能指定多個類別。 - 若想阻止其他類別繼承,可在類別上使用
final修飾詞。
實務開發技巧
妥善使用 extends 可以將共用功能集中於父類別,並在子類別中延伸或自訂行為。當你想在不修改既有程式碼的情況下加入新功能時,這也非常有幫助。
4. 方法覆寫與 super 關鍵字
在使用繼承時,常會出現需要變更父類別中已定義方法行為的情況,這稱為「方法覆寫」。在 Java 中,覆寫是透過在子類別中定義與父類別同名且參數列表相同的方法來完成的。
方法覆寫
覆寫方法時,通常會加入 @Override 註解。這能協助編譯器偵測意外的錯誤,例如方法名稱或簽名寫錯。
class Animal {
void eat() {
System.out.println("食べる");
}
}
class Dog extends Animal {
@Override
void eat() {
System.out.println("ドッグフードを食べる");
}
}
在此範例中,Dog 類別覆寫了 eat 方法。當對 Dog 實例呼叫 eat 時,輸出會是「ドッグフードを食べる」。
Dog dog = new Dog();
dog.eat(); // Displays: ドッグフードを食べる
使用 super 關鍵字
如果想在覆寫的方法內呼叫父類別原本的實作,請使用 super 關鍵字。
class Dog extends Animal {
@Override
void eat() {
super.eat(); // Calls the parent class’s eat()
System.out.println("ドッグフードも食べる");
}
}
這會先執行父類別的 eat 方法,然後再加入子類別的行為。
建構子與 super
若父類別的建構子帶有參數,子類別必須在自己的建構子第一行以 super(arguments) 明確呼叫它。
class Animal {
Animal(String name) {
System.out.println("Animal: " + name);
}
}
class Dog extends Animal {
Dog(String name) {
super(name);
System.out.println("Dog: " + name);
}
}
小結
- 覆寫是指在子類別中重新定義父類別的方法。
- 建議使用
@Override註解。 - 想重複使用父類別的方法實作時,請使用
super。 - 呼叫父類別建構子時也會使用
super。
5. 繼承的優點與缺點
在 Java 中使用繼承能為程式設計與開發帶來許多好處,但若使用不當也可能導致嚴重問題。以下將詳細說明其優缺點。
繼承的優點
提升程式碼可重用性
將共用的邏輯與資料放在父類別中,可免除在每個子類別裡重複相同程式碼,降低重複度,提升可維護性與可讀性。更容易擴充
當需要新增功能時,只要基於父類別建立新子類別即可,無需修改既有程式碼,減少變更衝擊與錯誤發生的機會。實現多型
繼承允許「父類別的變數可以指向子類別的實例」,藉此使用共通介面與多型行為,達成彈性的設計。
繼承的缺點
深層階層使設計變得複雜
如果繼承鏈過於深長,會難以了解行為是在哪裡定義的,進而使維護變得更困難。父類別的變更會影響所有子類別
修改父類別的行為可能會不小心在所有子類別中造成問題。父類別需要謹慎設計與更新。可能降低設計彈性
過度使用繼承會使類別緊密耦合,未來的變更變得困難。在某些情況下,使用「has-a」的組合關係比「is-a」的繼承更具彈性。
摘要
繼承功能強大,但若把它當成萬用解決方案,長期會產生問題。使用前務必確認是否真的存在「is-a」關係,僅在適當時機才使用繼承。
6. 繼承與介面的差異
Java 提供兩種重要機制來擴充與組織功能:類別繼承(extends)與介面(implements)。兩者皆支援程式碼重用與彈性設計,但其結構與使用目的有顯著差異。以下說明它們的差異以及如何選擇。
extends 與 implements 的差異
- extends(繼承)
- 只能繼承單一類別(單一繼承)。
- 可直接使用父類別的欄位與完整實作的方法。
代表「is-a」關係(例如:Dog 是 Animal)。
implements(介面實作)
- 可同時實作多個介面。
- 介面僅包含方法宣告(自 Java 8 起可有 default 方法)。
- 代表「can-do」關係(例如:Dog 可以叫、Dog 可以走)。
使用介面的範例
interface Walkable {
void walk();
}
interface Barkable {
void bark();
}
class Dog implements Walkable, Barkable {
public void walk() {
System.out.println("歩く");
}
public void bark() {
System.out.println("ワンワン");
}
}
在此範例中,Dog 類別實作了兩個介面 Walkable 與 Barkable,提供類似多重繼承的行為。 
為何需要介面
Java 禁止類別的多重繼承,因為當父類別定義相同的方法或欄位時會產生衝突。介面透過允許類別同時採用多種「型別」而不繼承衝突的實作,解決了這個問題。
正確使用方式
- 當類別之間存在明確的「is-a」關係時,使用
extends。 - 當需要在多個類別之間提供共通的行為合約時,使用
implements。
範例
- 「Dog 是 Animal」 →
Dog extends Animal - 「Dog 可以走也可以叫」 →
Dog implements Walkable, Barkable
摘要
- 一個類別只能繼承單一父類別,但可以實作多個介面。
- 依設計意圖在繼承與介面之間做選擇,能讓程式碼保持乾淨、彈性且易於維護。
7. 繼承的最佳實踐
在 Java 中,繼承功能強大,但若使用不當會使程式變得僵硬且難以維護。以下提供安全且有效使用繼承的最佳實踐與指引。
何時使用繼承 — 何時避免使用
- 使用繼承的情況:
- 存在明確的「is-a」關係(例如:Dog 是 Animal)。
- 想要重用父類別的功能並加以擴充。
想要消除重複程式碼,將共通邏輯集中管理。
避免使用繼承的情況:
- 只是為了程式碼重用而使用繼承(這往往會導致不自然的類別設計)。
- 「has-a」關係更為適切——此時應考慮使用組合(Composition)。
在繼承與組合之間的選擇
- 繼承(
extends):is-a 關係 - 範例:
Dog extends Animal - 當子類別真正代表父類別的一種型別時,使用此方式較為合適。
- 組合(has-a 關係)
- 範例:Car 有 Engine
- 在內部使用另一個類別的實例以加入功能。
- 更具彈性,且較易因應未來的變更。
防止濫用繼承的設計指導原則
- 不要讓繼承層級過深(建議維持在 3 層或以下)。
- 若有許多子類別繼承同一父類別,請重新評估該父類別的職責是否恰當。
- 務必考慮父類別的變更可能影響所有子類別的風險。
- 在使用繼承之前,先考慮介面與組合等替代方案。
使用 final 修飾詞限制繼承
- 在類別前加上
final可防止其被繼承。 - 在方法前加上
final可防止子類別覆寫。final class Utility { // This class cannot be inherited } class Base { final void show() { System.out.println("オーバーライド禁止"); } }
加強文件與註解
- 在 Javadoc 或註解中記錄繼承關係與類別設計意圖,可大幅提升未來維護的便利性。
小結
繼承固然便利,但必須有意識地使用。應不斷自問:「此類別真的屬於其父類別嗎?」若不確定,請考慮使用組合或介面作為替代方案。
8. 小結
到此為止,我們已詳細說明 Java 繼承與 extends 關鍵字——從基礎概念到實務運用。以下是本文重點的回顧。
- Java 繼承允許子類別取得父類別的資料與功能,從而實現高效且可重用的程式設計。
extends關鍵字說明了父類別與子類別之間的關係(即「is-a」關係)。- 方法覆寫與
super關鍵字使得可以擴充或自訂繼承的行為。 - 繼承具備許多優點,如程式碼重用、可擴充性與多型支援,但也有缺點,例如層級過深或結構過於複雜,以及變更影響範圍廣大。
- 了解繼承、介面與組合之間的差異,對於選擇適當的設計方式至關重要。
- 避免過度使用繼承;必須清楚說明設計意圖與理由。
繼承是 Java 物件導向程式設計的核心概念之一。透過了解其規則與最佳實踐,您即可在實務開發中有效運用。
9. 常見問題 (FAQ)
Q1:當類別在 Java 中被繼承時,父類別的建構子會發生什麼情況?
A1:如果父類別有無參數(預設)建構子,子類別的建構子會自動呼叫它。如果父類別只有帶參數的建構子,子類別必須在自己的建構子開頭使用 super(arguments) 明確呼叫它。
Q2:Java 能夠實作多重類別繼承嗎?
A2:不行。Java 不支援多重類別繼承。類別只能使用 extends 繼承單一父類別。然而,類別可以使用 implements 實作多個介面。
Q3:繼承與組合有何不同?
A3:繼承代表「is-a(是一種)關係」,子類別會重用父類別的功能與資料。組合代表「has-a(擁有)關係」,即一個類別內部包含另一個類別的實例。組合通常提供更大的彈性,在需要低耦合或未來可擴充的情況下較為適合。
Q4:final 修飾子是否限制繼承與覆寫?
A4:是的。若類別被標記為 final,則無法被繼承。若方法被標記為 final,則子類別無法覆寫該方法。這在確保行為一致性或出於安全考量時很有用。
Q5:如果父類別與子類別定義了相同名稱的欄位或方法,會發生什麼情況?
A5:若兩個類別都定義了相同名稱的欄位,子類別的欄位會隱藏(shadow)父類別的欄位。方法則不同:若簽名相同,子類別的方法會覆寫父類別的方法。需注意的是,欄位無法被覆寫,只能被隱藏。
Q6:如果繼承層級過深,會發生什麼情況?
A6:深層的繼承階層會使程式碼難以理解與維護,且不易追蹤邏輯所在。為了可維護的設計,應盡量保持繼承層級淺,並明確分離各角色。
Q7:覆寫與重載有何不同?
A7:覆寫是指在子類別中重新定義父類別的某個方法。重載則是在同一類別內定義多個同名但參數類型或數量不同的方法。
Q8:抽象類別與介面應如何分別使用?
A8:抽象類別適用於想在相關類別之間提供共用實作或欄位的情況。介面則用於定義多個類別可實作的行為合約。當需要共用程式碼時使用抽象類別;當要表示多種型別或需要多個「能力」時則使用介面。
