繼承是物件導向三大特性之一,但初學者常在「父類建構子呼叫時機」、「同名欄位存取」與「super 關鍵字用法」上產生困惑。本文透過一個完整範例,逐步拆解 Java 繼承的關鍵細節。
class A {
int num = 0; // 父類實例欄位
public A(int i) { // 父類帶參數建構子
System.out.println("aaaaaaaaaaaaaaaaaaa");
System.out.println("i = " + i);
num = i; // 將參數賦值給父類欄位
}
}
class B extends A {
int num = 0; // 子類同名欄位(遮蔽父類)
public B() {
super(10); // 顯式呼叫父類建構子,必須為第一行
System.out.println("bbbbbbbbbbbbbbbbbbbb");
System.out.println("num = " + num); // 存取子類欄位
System.out.println("super.num = " + super.num); // 存取父類欄位
}
}
public class C {
public static void main(String[] args) {
new B(); // 建立子類實例,觸發建構子鏈
}
}
new B() 呼叫子類無參建構子。super(10) 顯式呼叫父類 A(int) 建構子。A.num。B.num(預設 0)與 super.num(已被設為 10)。super(...) 或隱式 super())。super(...),否則編譯錯誤。B 類宣告 int num = 0 並不會覆寫父類的 num,而是在子類建立新欄位「遮蔽」父類同名欄位。
this.num / num → 存取子類欄位(預設 0)。super.num → 存取父類欄位(已被父類建構子設為 10)。建立 B 實例時,堆上會配置一個物件,內含父類欄位與子類欄位兩塊區域:
+---------------------+ | A part: num = 10 | ← 父類欄位 +---------------------+ | B part: num = 0 | ← 子類欄位(遮蔽) +---------------------+
| 錯誤寫法 | 問題 | 正確做法 |
|---|---|---|
| 子類建構子未呼叫 super(),父類無無參建構子 | 編譯失敗 | 顯式 super(參數) 呼叫對應父類建構子 |
| 誤以為子類欄位會覆蓋父類欄位值 | 邏輯錯誤,兩欄位並存 | 用 super.field 明確存取父類欄位;避免同名欄位 |
| 在父類建構子中呼叫可被覆寫的方法 | 子類欄位尚未初始化,導致 NPE 或錯誤行為 | 父類建構子只呼叫 final/private/static 方法 |
掌握 super 呼叫時機、欄位遮蔽機制與建構子鏈順序,是寫出正確、可維護繼承體系的基石。實務上建議:
super.field 區分。