1. 什麼是繼承?
繼承是一種機制,能夠在既有類別的基礎上建立新的類別,並「繼承」其屬性與方法。新的類別稱為子類別(或衍生類別),原本的類別稱為父類別(或基底類別)。
這就像是你用舊食譜做新蛋糕:把原先的精華都保留,再加上幾樣新食材——瞧,已經有一款「奶奶的祕密配方蛋糕」了。
- 父類別——包含通用的屬性與行為。
- 子類別——繼承這些內容,並可再加入自己的東西或改變行為。
例如:「貓是一種動物」:所有的貓都是動物,但不是所有動物都是貓。動物可以作為基底類別,貓則是它的子類別;狗也一樣。
再如:「圓形是一種圖形」:每個圓形都是圖形,但不是每個圖形都是圓形。圖形可作為父類別,而圓形與三角形則是其子類別。在 Java 中,這些關係是透過關鍵字 extends 來實作的。
為什麼需要繼承?
價值百萬的問題:為什麼不能直接把某個類別的程式碼複製到另一個類別?為什麼還要發明繼承?
- 重複使用程式碼:所有共通的部分都抽到基底類別。無須重複宣告欄位與方法——它們會自動出現在子類別中。比如在大學系統中,可以有一個基底類別「人」,至少包含姓名與生日,而「學生」與「教師」則是子類別,各自有自己的屬性。
- 擴充與專門化:可以僅在需要的地方新增屬性與方法,或覆寫既有行為。所有動物都會發出聲音,但貓會喵喵叫、狗會汪汪叫。同時,你也可以只在貓這個類別加上「爬樹」的能力(基底類別沒有這項能力)。
- 階層化結構:程式碼更有邏輯、也更容易理解。例如有 Animal、Dog、Cat 這幾個類別,就能一眼看出 Dog 與 Cat 是 Animal 的特例。
範例。如果 Java 沒有繼承,你就得在每個動物類別裡都複製像是 eat()、sleep() 這些方法,以及 name 欄位。要是明天想新增 breathe() 方法,就得修改數十個類別!有了繼承——只要在基底類別中加入「呼吸」的能力即可。
2. 繼承的語法:extends
如何在 Java 中宣告繼承?很簡單:使用關鍵字 extends。
class RoditelskiyKlass {
// 欄位與方法
}
class Podklass extends RoditelskiyKlass {
// 額外的欄位與方法
}
範例 1:動物
// 父類別
class Animal {
String name;
void eat() {
System.out.println(name + " 正在吃。");
}
}
// 子類別
class Cat extends Animal {
void meow() {
System.out.println(name + " 說:喵!");
}
}
這裡發生了什麼?
- Cat 類別繼承了 Animal 類別的所有欄位與方法。
- 因此 Cat 已經擁有 name 欄位與 eat() 方法,即使我們沒有顯式寫出來。
- 另外 Cat 也新增了自己的方法 meow()。
使用方式
public class Main {
public static void main(String[] args) {
Cat kitty = new Cat();
kitty.name = "巴爾西克";
kitty.eat(); // 巴爾西克 正在吃。 (繼承而來)
kitty.meow(); // 巴爾西克 說:喵! (自有方法)
}
}
大功告成!我們用最少的程式碼得到一個新類別,避免了重複。
3. 子類別究竟繼承了什麼?
在 Java 中子類別會繼承:
- 所有公開(public)與受保護(protected)的欄位與方法(來自基底類別)。
- 所有 package-private 的欄位與方法(若子類別位於同一個 package)。
不會繼承:
- 建構子(需要在子類別中顯式宣告)。
- 私有(private)的欄位與方法(它們只在基底類別內可見)。
- 靜態初始化區塊(不會被繼承,但每個類別都可以有自己的)。
- package-private 成員,如果子類別位於不同的 package。
示意
class Animal {
public String name;
private int age;
public void eat() {
System.out.println("在吃");
}
private void secret() {
System.out.println("祕密!");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("汪!");
}
}
- Dog 會繼承 name 欄位與 eat() 方法。
- Dog 看不到 age 欄位與 secret() 方法——因為它們是 private。
4. Java 中繼承的限制
Java 相當嚴格,也有自己的規則:
只能單一繼承類別:每個類別只能繼承自一個其他類別。不過,一個類別可以有任意多個子類別。
class Dog extends Animal { ... } // OK
class Dog extends Animal, Pet { ... } // 錯誤!不能這樣
介面可以多重繼承——可以,但這是另一個主題,我們之後再談。
Object 類別——是 Java 中所有類別的祖先。即使你沒有明寫 extends,你的類別也會隱式地繼承 Object。
5. 實用範例
讓我們繼續構建我們的「動物學」應用。
步驟 1. 基底類別 Animal
class Animal {
String name;
void eat() {
System.out.println(name + " 正在吃。");
}
}
步驟 2. 子類別 Cat
class Cat extends Animal {
void meow() {
System.out.println(name + " 說:喵!");
}
}
步驟 3. 子類別 Dog
class Dog extends Animal {
void bark() {
System.out.println(name + " 說:汪!");
}
}
步驟 4. 使用這些類別
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
cat.name = "穆爾卡";
cat.eat(); // 穆爾卡 正在吃。
cat.meow(); // 穆爾卡 說:喵!
Dog dog = new Dog();
dog.name = "沙里克";
dog.eat(); // 沙里克 正在吃。
dog.bark(); // 沙里克 說:汪!
}
}
我們得到了什麼?
- Cat 與 Dog 這兩個類別都使用了基底類別中的共用欄位 name 與方法 eat()。
- 每個類別都加入了各自的獨特行為:貓有 meow(),狗有 bark()。
- 如果需要改變動物進食的方式,只要修改 Animal 類別中的 eat() 方法——變更會自動套用到所有子類別。
6. 實用的小細節
視覺化:類別階層
可以這樣想像階層:
. Animal
/ \
Cat Dog
- Animal——基底類別,包含動物的共通部分。
- Cat 與 Dog——子類別,加入各自的特點。
談談建構子與繼承
建構子不會被繼承。如果你沒有在子類別中定義建構子,而父類別有無參數建構子,Java 會自動加入;若父類別只有帶參數的建構子,則必須在子類別中透過 super(...) 明確呼叫。簡短範例:
class Animal {
String name;
Animal(String name) {
this.name = name;
}
}
class Cat extends Animal {
Cat(String name) {
super(name); // 呼叫父類別的建構子
}
}
7. 使用繼承時的常見錯誤
錯誤 1:試圖同時繼承多個類別。
在 Java 中不能寫 class Cat extends Animal, Pet。只有一個父類別!
錯誤 2:以為 private 欄位與方法在子類別中也可見。
private 成員不會被存取(更精確地說,它們存在,但在子類別中無法存取)。如果要讓子類別能存取,請使用 protected。
錯誤 3:忘了呼叫父類別建構子。
如果基底類別沒有無參數建構子,而你又沒有顯式呼叫 super(...),就會出現編譯錯誤。
錯誤 4:為了「省程式碼」而使用繼承,卻不符合語意。
如果類別之間不存在「is-a」關係(is-a),最好不要使用繼承。例如,「人」不應該繼承自「汽車」,即使它們都有 speed 欄位。
GO TO FULL VERSION