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註解確保正確覆寫 - 方法簽名必須完全相同
- 回傳型別可以是共變型別
- 存取權限不能更嚴格
final、static、private方法不能覆寫- 覆寫是執行時決定(動態綁定)
- 多載是編譯時決定(靜態綁定)