CodeGym /課程 /JAVA 25 SELF /繼承與方法多載的常見錯誤

繼承與方法多載的常見錯誤

JAVA 25 SELF
等級 23 , 課堂 1
開放

1. 繼承中的錯誤

繼承是 OOP 的基石之一,但也是新手最常踩雷的主題之一。我們來看看經典錯誤,並學會如何避免。

沒有呼叫基底類別的建構式 (super(...))

當你建立子類別時要記得:基底類別可能要求透過建構式進行特定的初始化。若基底類別沒有預設(無參數)建構式,則在子類別的建構式中一定要super(...) 明確呼叫基底類別的建構式。

錯誤範例:

class Animal {
    private String name;
    public Animal(String name) {
        this.name = name;
    }
}

class Dog extends Animal {
    // 錯誤!Animal 沒有無參數(預設)建構式
    public Dog() {
        // super(); // 編譯器會自動插入 super(),但基底類別並沒有這樣的建構式!
    }
}

如何修正:

class Dog extends Animal {
    public Dog(String name) {
        super(name); // 這樣就沒問題!
    }
}

說明:
如果基底類別只有帶參數的建構式,編譯器不會自動產生無參數建構式。這常是編譯錯誤的原因。

試圖繼承自 final 類別或覆寫 final 方法

在 Java 中可以將類別或方法宣告為 final。這表示:

  • 類別不可被繼承。
  • 方法不可在子類別中覆寫(override)。

錯誤範例:

final class Cat {}

// 編譯錯誤!
class Tiger extends Cat { 
    // ...
}
class Animal {
    public final void sleep() {
        System.out.println("Zzz...");
    }
}

class Dog extends Animal {
    // 編譯錯誤!
    @Override
    public void sleep() {
        System.out.println("Dog is sleeping...");
    }
}

說明:
如果你看到「cannot inherit from final」、「cannot override final method」等錯誤,請檢查修飾詞!

違反里氏替換原則(Liskov Substitution Principle)

聽起來很可怕,但實際上意思是:子類別的物件必須能像基底類別的物件一樣行為,而不破壞程式邏輯。常見錯誤是把方法覆寫成與基底預期不一致的行為。

範例:

class Bird {
    public void fly() {
        System.out.println("我在飛!");
    }
}

class Penguin extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("企鵝不會飛!");
    }
}

問題在哪裡?
處理 Bird 的程式碼會假設任何鳥都會飛;但若傳入 Penguin,程式可能會在執行時失敗。

較佳作法:
此時應重新檢視階層設計,或改用介面/組合。

2. 方法多載(overloading)的常見錯誤

多載是同一個類別中有多個同名但參數不同的方法。看似簡單,卻也容易踩坑。

把覆寫誤寫成多載(簽章錯誤)

新手常想覆寫(override)基底方法,但不小心改了參數。結果變成多載,而不是覆寫——多型不起作用!

錯誤範例:

class Animal {
    public void makeSound() {
        System.out.println("Some sound");
    }
}

class Dog extends Animal {
    // 原本想覆寫,卻變成多載!
    public void makeSound(String extra) {
        System.out.println("Bark! " + extra);
    }
}

問題:
呼叫 dog.makeSound() 會執行父類別的方法,而不是你新增的那個。
呼叫 dog.makeSound("loudly") 會走到多載的方法,但多型不起作用!

最佳實務:
請使用註解 @Override——若你在簽章上出錯,編譯器會立刻提醒。

@Override
public void makeSound() { /* ... */ }

多載的非直觀行為(自動型別轉換、呼叫歧義)

當參數型別同時符合多個多載版本時,Java 有時會選擇與你預期不同的方法。

public class OverloadDemo {
    public void print(int x) {
        System.out.println("int: " + x);
    }
    public void print(double x) {
        System.out.println("double: " + x);
    }
}

OverloadDemo demo = new OverloadDemo();
demo.print(5);     // int: 5
demo.print(5.0);   // double: 5.0
demo.print(5L);    // long -> double: double: 5.0

問題:
如果呼叫 demo.print(5L),Java 會選擇 print(double x)(因為 long 轉成 double 比轉成 int 更合適)。
若同時存在參數型別為 ObjectIntegerint 的多載,使用 null 呼叫可能導致編譯錯誤:「reference to print is ambiguous」。

僅以回傳型別做區分的同名方法(編譯錯誤)

在 Java 中不能宣告兩個只有回傳型別不同、參數相同的同名方法!

public class Demo {
    // 編譯錯誤!
    public int foo() { return 1; }
    public String foo() { return "hello"; }
}

說明:
用於多載判斷的方法簽章是名稱 + 參數,回傳型別不被考慮。編譯器無法判斷你要呼叫哪一個方法。

3. 最佳實務

為了避免在繼承與多載上踩雷,請遵循以下建議:

覆寫方法時務必使用 @Override 註解

這不僅提高可讀性,也能防止簽章錯誤;一旦你改了參數或方法名稱,編譯器會立刻告知。

@Override
public void makeSound() {
    System.out.println("Bark!");
}

清楚區分多載與覆寫

  • 覆寫(override):改變父類別方法的行為——簽章必須完全相同。
  • 多載(overload):新增同名方法,但參數不同。

對照表:

多載(overloading) 覆寫(overriding)
位置 同一類別/繼承層級內 在子類別中
方法名稱 相同 相同
參數 不同 相同
回傳型別 可以不同 必須相同/相容
註解 非必須 @Override 建議使用

避免過度使用多載

如果某個方法有太多多載版本,程式會變得難讀且混亂。當選項過多時,最好使用參數物件或 Builder 模式。

4. 範例:寵物管理系統

假設你要做一個簡單的寵物管理系統。你有基底類別 Pet 與子類別 CatDog

public class Pet {
    private String name;
    public Pet(String name) {
        this.name = name;
    }
    public void speak() {
        System.out.println(name + " 發出不明的聲音。");
    }
}

public class Cat extends Pet {
    public Cat(String name) {
        super(name);
    }
    @Override
    public void speak() {
        System.out.println(getName() + " 說:喵!");
    }
    // 錯誤:Pet 中沒有 getName() 方法!
}

典型錯誤:
在嘗試覆寫方法時,卻呼叫了基底類別沒有的方法。應該新增一個 getter:

public class Pet {
    private String name;
    public Pet(String name) { this.name = name; }
    public String getName() { return name; }
    public void speak() { System.out.println(name + " 發出不明的聲音。"); }
}

現在一切正常,可以使用多型:

Pet myPet = new Cat("Barsik");
myPet.speak(); // Barsik 說:喵!

5. 繼承與多載的常見錯誤彙總

錯誤一:忘了呼叫 super(...)
如果基底類別的建構式含有重要邏輯而你沒有呼叫,程式可能行為異常,甚至無法編譯。

錯誤二:覆寫了錯的方法。
原本想改變父類別方法的行為,卻新增了一個參數不同或名字相近的新方法。結果:舊方法照樣執行,而你的新方法沒有人呼叫。

錯誤三:嘗試覆寫 final 方法。
Java 不允許這麼做,這是好事!但如果看到編譯錯誤——請檢查是否有 final

錯誤四:把方法多載得讓人看不懂。
當你有 10 個 calculate 的多載版本,而且你自己也搞混時——是時候停下來考慮重構了。

錯誤五:違反里氏替換原則。
如果子類別改變了基底類別的行為語意,整個架構都可能歪掉。比如有個 Shape 類別有 getArea(),而子類別 BrokenShape 回傳 -1,就可能造成奇怪的 bug。

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