Java 方法覆寫 (Method Overriding)

方法覆寫是指子類別重新定義父類別的方法,讓子類別可以有自己的實作。

基本概念

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

class Dog extends Animal {
    @Override
    public void speak() {
        System.out.println("汪汪!");
    }
}

class Cat extends Animal {
    @Override
    public void speak() {
        System.out.println("喵喵!");
    }
}

// 使用
Animal dog = new Dog();
Animal cat = new Cat();

dog.speak();  // 汪汪!
cat.speak();  // 喵喵!

@Override 註解

@Override 是可選的,但強烈建議使用:

class Parent {
    public void method() {
        System.out.println("父類別方法");
    }
}

class Child extends Parent {
    @Override
    public void method() {  // ✓ 編譯器會檢查是否真的覆寫
        System.out.println("子類別方法");
    }

    @Override
    public void methd() {  // ✗ 編譯錯誤:拼錯了,不是覆寫
        System.out.println("錯誤");
    }
}

@Override 的好處:

  • 編譯器會檢查方法簽名是否正確
  • 提高程式碼可讀性
  • 避免拼寫錯誤

覆寫規則

1. 方法簽名必須相同

方法名稱和參數列表必須完全一致:

class Parent {
    public void method(int x) { }
}

class Child extends Parent {
    @Override
    public void method(int x) { }     // ✓ 覆寫

    public void method(String x) { }  // ✗ 這是多載,不是覆寫
}

2. 回傳型別規則

回傳型別必須相同或是子型別(共變回傳型別):

class Animal { }
class Dog extends Animal { }

class Parent {
    public Animal getAnimal() {
        return new Animal();
    }
}

class Child extends Parent {
    @Override
    public Dog getAnimal() {  // ✓ Dog 是 Animal 的子類別
        return new Dog();
    }
}

基本型別必須完全相同:

class Parent {
    public int getValue() { return 0; }
}

class Child extends Parent {
    @Override
    public long getValue() { return 0; }  // ✗ 編譯錯誤
}

3. 存取修飾子規則

覆寫的方法不能有更嚴格的存取權限:

class Parent {
    protected void method() { }
}

class Child extends Parent {
    @Override
    public void method() { }     // ✓ public 比 protected 更寬鬆

    @Override
    protected void method() { }  // ✓ 相同

    @Override
    private void method() { }    // ✗ private 比 protected 更嚴格
}

存取權限順序(寬鬆到嚴格): public > protected > (default) > private

4. 例外規則

覆寫的方法不能拋出更多的 checked exception:

class Parent {
    public void method() throws IOException { }
}

class Child extends Parent {
    @Override
    public void method() throws IOException { }         // ✓ 相同

    @Override
    public void method() { }                            // ✓ 不拋出例外

    @Override
    public void method() throws FileNotFoundException { } // ✓ 子類別例外

    @Override
    public void method() throws Exception { }           // ✗ 更廣泛的例外
}

不能覆寫的方法

final 方法

class Parent {
    public final void method() {
        System.out.println("不能被覆寫");
    }
}

class Child extends Parent {
    @Override
    public void method() { }  // ✗ 編譯錯誤
}

static 方法

靜態方法屬於類別而非物件,不能被覆寫(只能被隱藏):

class Parent {
    public static void staticMethod() {
        System.out.println("Parent");
    }
}

class Child extends Parent {
    public static void staticMethod() {  // 這是隱藏,不是覆寫
        System.out.println("Child");
    }
}

Parent.staticMethod();  // Parent
Child.staticMethod();   // Child

Parent p = new Child();
p.staticMethod();       // Parent(根據參考型別決定)

private 方法

私有方法不會被繼承,所以不能覆寫:

class Parent {
    private void privateMethod() {
        System.out.println("Parent private");
    }
}

class Child extends Parent {
    // 這不是覆寫,而是新方法
    private void privateMethod() {
        System.out.println("Child private");
    }
}

覆寫 vs 多載

特性覆寫 (Overriding)多載 (Overloading)
發生位置子類別與父類別之間同一個類別中
方法名稱相同相同
參數列表相同不同
回傳型別相同或共變型別可以不同
解析時機執行時(動態綁定)編譯時(靜態綁定)
存取修飾子不能更嚴格無限制
class Calculator {
    // 多載:同類別,不同參數
    public int add(int a, int b) { return a + b; }
    public double add(double a, double b) { return a + b; }
}

class Parent {
    public void method(int x) { }
}

class Child extends Parent {
    // 覆寫:子類別,相同參數
    @Override
    public void method(int x) { }
}

呼叫父類別方法

使用 super 呼叫被覆寫的父類別方法:

class Animal {
    public void speak() {
        System.out.println("動物聲音");
    }
}

class Dog extends Animal {
    @Override
    public void speak() {
        super.speak();  // 先呼叫父類別方法
        System.out.println("汪汪!");
    }
}

多型與覆寫

覆寫是實現多型的關鍵:

abstract class Shape {
    abstract double area();
}

class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    double area() {
        return Math.PI * radius * radius;
    }
}

class Rectangle extends Shape {
    private double width, height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    double area() {
        return width * height;
    }
}

// 多型使用
Shape[] shapes = {
    new Circle(5),
    new Rectangle(3, 4)
};

for (Shape s : shapes) {
    System.out.println("面積:" + s.area());
}
// 面積:78.53981633974483
// 面積:12.0

實際範例

樣板方法模式

abstract class Report {
    // 樣板方法
    public final void generate() {
        fetchData();
        processData();
        format();
        output();
    }

    protected abstract void fetchData();
    protected abstract void processData();

    protected void format() {
        System.out.println("預設格式化");
    }

    protected void output() {
        System.out.println("輸出到控制台");
    }
}

class SalesReport extends Report {
    @Override
    protected void fetchData() {
        System.out.println("取得銷售資料");
    }

    @Override
    protected void processData() {
        System.out.println("計算銷售統計");
    }

    @Override
    protected void format() {
        System.out.println("PDF 格式");
    }
}

equals 和 hashCode

class Person {
    private String name;
    private int age;

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

重點整理

  • 覆寫讓子類別重新定義父類別的方法
  • 使用 @Override 註解確保正確覆寫
  • 方法簽名必須完全相同
  • 回傳型別可以是共變型別
  • 存取權限不能更嚴格
  • finalstaticprivate 方法不能覆寫
  • 覆寫是執行時決定(動態綁定)
  • 多載是編譯時決定(靜態綁定)