在前幾章中,我們已經學會了如何使用類創建新類型,並透過繼承機制複用代碼。本章將深入探討 Java 的類型系統核心——類型檢查、基本類型轉換與引用類型的向上/向下轉型,並揭開多態這一物件導向基石的真實面貌。
Java 是一門強類型語言。無論是變數、方法參數、返回值還是區域變數,皆須在編譯期明確宣告類型。編譯器會嚴格檢查類型相容性,任何類型不匹配的操作都將導致編譯錯誤,從而在開發階段就攔截潛在缺陷。
public class Test {
public static void main(String[] args) {
Human aPerson;
aPerson = new Cup(); // 編譯錯誤:found: Cup, required: Human
}
}
class Human {
private int height;
public Human(int h) { this.height = h; }
public int getHeight() { return height; }
public void growHeight(int h) { height += h; }
}
class Cup {
private int water = 0;
public void addWater(int w) { water += w; }
public void drinkWater(int w) { water -= w; }
}
錯誤訊息直觀地提醒:found: Cup required: Human。這正是強類型帶來的安全網。
基本類型之間的轉換分為擴寬轉換與縮小轉換,核心在於精度是否損失。
從低精度向高精度轉換(如 int → double),不會遺失資訊,Java 自動處理:
int a = 3;
double b = a; // 自動擴寬
System.out.println(b); // 3.0
從高精度向低精度(如 double → int)可能截斷小數部分需開發者明確聲明:
int a = (int) 1.23; // 顯式縮小,結果為 1
System.out.println(a);
子類別參考可隱式轉型為父類別參考,這稱為向上轉型。關鍵在於:編譯期類型是父類,運行期物件仍是子類,JVM 會根據實際物件類型動態綁定方法。
class Cup {
public void addWater(int w) { System.out.println("Cup.addWater"); }
public void drinkWater(int w) { System.out.println("Cup.drinkWater"); }
}
class BrokenCup extends Cup {
@Override public void addWater(int w) { System.out.println("shit, broken cup"); }
@Override public void drinkWater(int w) { System.out.println("om...num..., no water inside"); }
}
public class Test {
public static void main(String[] args) {
Cup aCup;
BrokenCup aBrokenCup = new BrokenCup();
aCup = aBrokenCup; // 向上轉型
aCup.addWater(10); // 輸出: shit, broken cup
}
}
多態正是這種「一個介面,多種實作」的能力:aCup 雖被宣告為 Cup,卻能正確呼叫 BrokenCup 的覆寫方法。這對應現實世界的分類邏輯——「鳥是動物」、「車是交通工具」,子類別物件本質上就是父類別物件。
class Human {
void drink(Cup aCup, int w) { aCup.drinkWater(w); }
}
public class Test {
public static void main(String[] args) {
Human guest = new Human();
BrokenCup hisCup = new BrokenCup();
guest.drink(hisCup, 10); // 實際呼叫 BrokenCup.drinkWater
}
}
Human.drink() 只依賴 Cup 介面,卻能正確處理任何 Cup 子類別。這就是面向介面程式設計的威力——編譯期依賴抽象,運行期綁定具體。
將父類別參考轉回子類別參考稱為向下轉型,前提是該參考實際指向的物件確實是目標子類別,否則拋出 ClassCastException。實務上常搭配 instanceof 守衛:
if (aCup instanceof BrokenCup) {
BrokenCup bc = (BrokenCup) aCup; // 安全向下轉型
bc.addWater(5);
}
Java 中所有類別隱式繼承 java.lang.Object。它提供了 toString()、equals()、hashCode() 等通用方法,我們可覆寫以賦予業務含義。
利用這一特性,我們可以編寫接受 Object 參數的通用方法,透過向上轉型傳入任意物件:
public void printObject(Object obj) {
System.out.println(obj.toString()); // 多態調用各類別覆寫的 toString()
}
instanceof。toString()/equals() 為良習。進階提示:多態的底層實現依賴RTTI(Run-Time Type Identification),JVM 在方法區儲存類型元資料,調用時查閱虛方法表完成動態分派。後續章節將深入剖析。