CodeGym /課程 /JAVA 25 SELF /使用 super:呼叫基底類別的建構子與方法

使用 super:呼叫基底類別的建構子與方法

JAVA 25 SELF
等級 17 , 課堂 2
開放

1. 使用 super 呼叫基底類別的方法

當你建立子類別時,有時會需要存取基底類別的欄位或方法,特別是當你已經在子類別中將它們覆寫或「遮蔽」時。為此,Java 提供了專門的關鍵字 — super

打個比方,super 就像「媽媽,幫幫我!」,當你在子類別中想要明確呼叫父類別中定義的內容時,就會用到它。

想像你有一個類別 Animal,其中的方法 eat() 只是輸出 "動物在吃東西"。而在類別 Cat 中,你希望貓先做點自己的事(例如喵一聲),然後仍然執行標準的「動物在吃東西」。這時就會用到 super.eat()

當你在子類別中覆寫方法,但仍想在方法內呼叫基底類別的實作時,請使用 super.methodName()

範例:擴充行為

class Animal {
    void eat() {
        System.out.println("動物在吃東西");
    }
}

class Cat extends Animal {
    @Override
    void eat() {
        System.out.println("貓正在聞食物...");
        super.eat(); // 呼叫 Animal 的 eat()
        System.out.println("貓滿足地發出呼嚕聲");
    }
}

這怎麼運作?

  • 當對 Cat 型別的物件呼叫 eat() 時,會先執行 Cat.eat() 的程式碼,也就是子類別自己的方法。
  • 在該方法內,我們明確呼叫 super.eat(),也就是父類別 Animal 的實作。
  • 這讓我們能在不遺漏「父類別邏輯」的前提下,加入額外的行為。

實作:在應用程式中使用

假設在我們的教學應用程式中,有基底類別 Animal 以及子類別 DogCat。我們希望在餵食動物時,既執行通用行為(例如提升飽足度),也執行各動物的特殊行為。

class Animal {
    int satiety = 0;

    void eat() {
        satiety += 10;
        System.out.println("動物在吃東西。飽足度: " + satiety);
    }
}

class Dog extends Animal {
    @Override
    void eat() {
        System.out.println("狗在吃之前搖尾巴");
        super.eat();
    }
}

現在如果呼叫 dog.eat(),你會看到兩則訊息,且 satiety 也會正確增加。

2. 使用 super 存取基底類別的欄位

如果你在子類別中宣告了與父類別同名的欄位,它會「遮蔽」父類別的欄位。有時候你需要存取基底類別中的原始欄位 — 這時就會用到 super.fieldName

範例:欄位遮蔽

class Animal {
    String name = "動物";
}

class Cat extends Animal {
    String name = "貓";

    void printNames() {
        System.out.println("來自 Cat 的名稱: " + name);
        System.out.println("來自 Animal 的名稱: " + super.name);
    }
}

呼叫 new Cat().printNames(); 會輸出:

來自 Cat 的名稱: 貓
來自 Animal 的名稱: 動物

在實務上,除非必要,不建議「遮蔽」欄位,但了解這種情況仍然很重要。

3. 透過 super(...) 呼叫基底類別的建構子

階層中的物件是如何被建立的?

當你建立子類別的物件時,會先呼叫基底類別的建構子,然後才呼叫子類別的建構子。這是為了讓所有欄位都能正確初始化,因為子類別會「繼承」父類別的一部分狀態。

顯式呼叫基底類別的建構子

如果基底類別有無參數的建構子 — 那就很簡單:Java 會在執行子類別建構子之前自動呼叫它。但如果父類別沒有無參數建構子,你必須透過 super(...) 明確呼叫適當的建構子。

範例:

class Animal {
    String name;

    Animal(String name) {
        this.name = name;
        System.out.println("已建立動物: " + name);
    }
}

class Cat extends Animal {
    Cat(String name) {
        super(name); // 必須!Animal 並沒有無參數建構子
        System.out.println("已建立貓咪: " + name);
    }
}

呼叫 new Cat("Murka") 會輸出:

已建立動物: Murka
已建立貓咪: Murka

重要:透過 super(...) 呼叫父類別建構子,必須是子類別建構子的第一行。如果你試圖在它之前寫其他東西,編譯器會提出錯誤並提醒你。

如果沒有明確呼叫呢?
如果父類別只有帶參數的建構子,而你沒有透過 super(...) 明確呼叫它,編譯器會產生錯誤:"constructor Animal in class Animal cannot be applied to given types"

4. 實用細節

什麼時候該用 super

用於擴充,而非取代行為。
有時你並不想完全取代方法的行為,只想「擴充」它,例如在父類別邏輯之前或之後多做一些事。在這種情況下,請在已覆寫的方法中使用 super.methodName()

用於初始化繼承而來的欄位。
如果父類別有必須初始化的欄位(例如動物的名稱),請務必透過 super(...) 呼叫父類別的建構子並傳入所需參數。

用於存取被遮蔽的欄位/方法。
如果你因某些原因「遮蔽」了父類別的欄位或方法,但仍需要存取它,請使用 super.fieldNamesuper.methodName()

super 的限制與注意事項

  • 透過 super(...) 呼叫父類別建構子只能在建構子內,而且只能是第一行
  • 無法在子類別的建構子之外呼叫父類別的建構子。
  • 如果沒有明確呼叫 super(...),Java 會嘗試呼叫父類別的無參數建構子(若存在)。
  • 關鍵字 super 不能在靜態方法中使用 — 只能在非靜態(實例)方法與建構子中使用。
  • 如果父類別的方法或欄位是 privatesuper 也無能為力:私有成員無法被存取。

5. 練習範例

範例 1:使用 super 擴充方法

class Animal {
    void makeSound() {
        System.out.println("動物發出聲音");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        super.makeSound(); // 先執行標準動作
        System.out.println("狗在叫: 汪汪!");
    }
}

範例 2:呼叫基底類別的建構子

class Vehicle {
    String brand;

    Vehicle(String brand) {
        this.brand = brand;
        System.out.println("交通工具: " + brand);
    }
}

class Car extends Vehicle {
    int year;

    Car(String brand, int year) {
        super(brand); // 呼叫父類別的建構子
        this.year = year;
        System.out.println("汽車 " + brand + ", 年份: " + year);
    }
}
Car car = new Car("Toyota", 2023);
// 輸出:
// 交通工具: Toyota
// 汽車 Toyota, 年份: 2023

範例 3:經典錯誤——忘了呼叫 super(...)

class Animal {
    String name;

    Animal(String name) {
        this.name = name;
    }
}

class Cat extends Animal {
    Cat() {
        // super(); // 錯誤!沒有無參數的 Animal() 建構子
        // 必須明確呼叫 super(name)
        super("無名的貓");
    }
}

6. 使用 super 時的常見錯誤

錯誤 1:在建構子中,super(...) 不是第一行。
Java 嚴格要求透過 super(...) 呼叫父類別建構子必須是子類別建構子的第一行。如果你嘗試在它之前執行其他操作(例如列印訊息),編譯器會報錯。

錯誤 2:父類別中沒有合適的建構子。
如果基底類別沒有無參數建構子,而你也沒有透過 super(...) 呼叫其他建構子,編譯器將無法產生預設呼叫,並會回報錯誤。

錯誤 3:嘗試透過 super 存取父類別的私有成員。
關鍵字 super 並不會賦予你對私有欄位或方法的魔法存取權。凡是宣告為 private 的成員,對子類別而言依然不可見。

錯誤 4:在不清楚的情況下遮蔽欄位與方法。
如果你在子類別中宣告了與父類別同名的欄位或方法,卻忘了這件事,可能會得到出乎意料的結果。請記住,在此情況下,若要存取父類別成員,只能透過 super

錯誤 5:在靜態方法中使用 super
在靜態方法中不能使用 super,因為它們不屬於某個具體物件。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION