Java 繼承與 protected 關鍵字,還有 Upcasting 入門【Thinking in Java筆記(6-3)】
你有沒有想過,為什麼程式設計時我們需要「父類別/子類別」的概念?
- 繼承:就好比爸爸的特質會傳給小孩,子類別自動擁有父類別的屬性與功能。
- 多型:透過向上轉型(Upcasting),我們可以用同一個介面,操作不同類型的物件。
讓我們用最簡單的方式,從 為什麼要學 開始聊起
為什麼要學繼承?
想像你在辦公室裡,要處理各種動物。你不想為每一種動物都重寫 eat()
、sleep()
。這時候,繼承就像是把動物通用行為寫在一張父親清單上,大家一樣照著做,少了重複工作,也方便管理
想像如果每個動物寫一份吃、睡方法,程式碼會變成什麼樣子?
這就是繼承的價值:
- 減少重複:共用父類別功能
- 統一管理:父類別改了,子類別全同步
- 結構清晰:想像家譜圖,誰是誰的子代,一目了然
protected:家族內部的小祕密
當我們想把某些資料對外隱藏,又允許子類別使用,就需要 protected
- 對外部世界來說,
protected
成員就像上鎖的門,不能直接進入 - 但同一家族(父類別和子類別),以及同個包裝(package)裡的其他類別,拿到鑰匙可以進去
class Villain {
private String name;
protected void set(String nm) { name = nm; }
public Villain(String name) { this.name = name; }
public String toString() {
return "I'm a Villain and my name is " + name;
}
}
public class Orc extends Villain {
private int orcNumber;
public Orc(String name, int orcNumber) {
super(name);
this.orcNumber = orcNumber;
}
public void change(String name, int orcNumber) {
set(name); // Available because it's protected
this.orcNumber = orcNumber;
}
public String toString() {
return "Orc " + orcNumber + ": " + super.toString();
}
public static void main(String[] args) {
Orc orc = new Orc("Limburger", 12);
print(orc);
orc.change("Bob", 19);
print(orc);
}
} /* Output:
Orc 12: I'm a Villain and my name is Limburger
Orc 19: I'm a Villain and my name is Bob
*/
想像你家有一間密室,只有家族成員能進去拿工具、調整傢俱;訪客則只能在門口觀望,無法改動任何東西
再想像每個寄出的包裹,都有一把專屬鑰匙,只有寄件人和收件人能打開;其他人既看不到也改不了裡面的物品。這就像 protected
,僅允許「家族」成員或同一包裝內的類別訪問
Villain
類別有一個protected
方法set(String nm)
,子類別Orc
可以訪問並使用這個方法Orc
的change()
方法使用了set()
來修改name
,並修改了自己的orcNumber
Orc
的toString()
方法覆寫了Villain
的toString()
,並透過super.toString()
調用父類別的方法
這展示了 protected
如何允許子類別訪問父類別的成員,同時仍然對其他類別保持隱藏
Upcasting:專用到通用的魔法
想像你擁有一把 風笛(Wind
),但調音師的工作單上只寫了「樂器」(Instrument
)而已,沒有特別標明是哪種樂器
這時候,你只需要把風笛當成一般樂器送去調音——調音師不會在意它原本是風笛還是鋼琴,他只會根據「樂器的方法」來進行操作。這個「把更專用的風笛,當作更通用的樂器來使用」的過程,就是 向上轉型(Upcasting):
class Instrument {
public void play() {}
static void tune(Instrument i) {
// ...
i.play();
}
}
// Wind objects are instruments
// because they have the same interface:
public class Wind extends Instrument {
public static void main(String[] args) {
Wind flute = new Wind();
Instrument.tune(flute); // Upcasting
}
}
步驟:
- 在 Java 中,
Wind flute = new Wind();
會先建立一個風笛物件 - 當我們呼叫
Instrument.tune(flute);
時,編譯器會自動把Wind
型別的flute
視為它的父類別Instrument
,完成向上轉型 - 在
tune()
方法裡,只能呼叫Instrument
類別上定義的方法(像play()
),因為編譯期只知道它是一個Instrument
這樣做有幾種好處:
- 統一介面:不用為每種樂器都寫不同的
tuneWind()
、tuneString()
方法,只要一個tune(Instrument i)
就搞定所有 - 靈活擴充:以後新增鼓(
Drum
)、吉他(Guitar
)等,只要繼承自Instrument
,都能直接傳給tune()
注意:向上轉型後就不能呼叫子類別特有的方法了。比如如果Wind
有adjustReed()
,在tune(instrument)
中就看不到,因為編譯期認為它是個Instrument
透過這個流程,我們把專用(風笛)的功能,放進通用(樂器)的模組裡,使程式設計更具彈性,也能同時兼顧擴充與維護
向上轉型的概念
- 定義:將子類別型別的引用賦值給父類別型別的參考
Wind flute = new Wind(); // 建立 Wind 物件
Instrument inst = flute; // 自動向上轉型,flute 被當作 Instrument
inst.play(); // 呼叫父類別 play(),執行 Wind 中的實作
- 安全性:向上轉型是安全的,因為任何子類別物件都「本來就是」父類別的一種,擁有父類別的所有方法和屬性,不會缺少功能
- 限制條件:轉型後只能呼叫父類別定義的方法和屬性。例如若
Wind
類有adjustReed()
,執行:
inst.adjustReed(); // 編譯錯誤:Instrument 沒有這個方法
只能先向下轉型(Downcasting)回 Wind
,才能調用子類別專屬方法
為何稱為向上轉型
在繼承結構圖中,父類別通常置於上方,子類別位於下方;將子類別標籤換成父類別標籤,就像從畫面中的下方向上方移動,所以叫 向上轉型(Upcasting)

- 子→父:把更特殊化的物件當作更一般化的類別來看待
- 視覺想像:就好像在一張家譜圖上,從子孫節點往祖先節點「向上走」
核心重點:
- 專用→通用:轉型後只剩父類別的方法與屬性可用,子類別獨有功能將被「隱藏」
- 多型基石:Upcasting 是實現多型的關鍵,透過它可以用統一的父介面操作各種子物件
當你需要回到子類別的獨有功能,就要做「向下轉型(Downcasting)」,方向如同從家譜圖的上方滑回下方
向上轉型的意義
子類別的物件可以被當成父類別使用,藉此讓程式在執行時看不到子類別的特殊功能,而只專注於父類別的共同行為
- 想像你有一支只能寫字的魔法筆(
MagicPen
),但你的同事只知道它是橡皮擦(Eraser
)的一種——他只需要「擦除功能」。 - 你把這支魔法筆當成普通橡皮擦交給他,他就能呼叫
erase()
功能。他不會知道這支筆額外能畫畫,但這不影響他的工作 - 為什麼重要?
- 多型基石:集合(如陣列或清單)存放各式子類別物件,例如
List<Eraser> list
,只要它們都繼承自Eraser
,就能一起迭代呼叫erase()
- 統一介面:不必為每種子類別都寫不同的方法簽章,降低程式碼複雜度
- 靈活擴充:以後新增
SmartEraser
、GelEraser
,只要繼承自Eraser
,馬上能支援既有程式
- 多型基石:集合(如陣列或清單)存放各式子類別物件,例如
- 實際案例:
- 圖形繪製:定義
Shape
父類別,子類別有Circle
、Square
、Triangle
。使用drawAll(List<Shape> shapes)
,就能一次畫出所有形狀 - 付款系統:定義
PaymentMethod
,實作CreditCard
、Paypal
、Bitcoin
。呼叫process(PaymentMethod p)
,統一處理付款流程
- 圖形繪製:定義
這就是「把特殊當一般、再統一操作」的魔法,熟悉之後設計程式就能自然想到:需要多型時,就先考慮 Upcasting
小結:打造靈活又安全的物件導向程式
- 繼承 讓子類別自動擁有父類別共通行為,減少重複程式碼,修改時更集中化,讓程式結構更清晰易管理
- protected 提供「家族成員專屬通道」,隱藏內部實作又允許子類別安全存取,達成封裝與彈性的平衡
- 向上轉型(Upcasting) 將子類別視作父類別使用,是多型的基礎;透過統一介面,可以一鍵處理各種不同物件,極大提升擴充性與維護性
下次設計類別時,思考哪些行為可以抽到父類別,哪些成員需要對子類別開放,並評估是否用 Upcasting 統一操作流程
掌握這三大基礎,你的 Java 程式將更 乾淨、靈活、易維護,也更接近「物件導向大師」的境界
Comments ()