依據 Luca Cardelli 與 Peter Wegner 的經典分類,多態可分為兩大類四小類:
| 大類 | 小類 | 說明 | Java 支援度 |
|---|---|---|---|
| 特定多態 | 強制 | 隱式型別轉換(如 double + int → double + double) | 支援 |
| 重載 | 同名稱表達不同意義(方法多載、運算子多載) | 支援方法多載,不支援使用者定義運算子多載 | |
| 通用多態 | 參數 | 泛型程式設計(如 List<T>),單一演算法適用多型別 | Java 5 前不支援真正參數多態,僅用 Object 模擬 |
| 包含 | 子型別多態,透過繼承層級實現 | 核心支援,即「介面對介面程式設計」的基石 |
本文聚焦於 包含多態(子型別多態),探討其類型導向的本質。
public class Base {
public String m1() { return "Base.m1()"; }
public String m2(String s) { return "Base.m2(" + s + ")"; }
}
interface IType {
String m2(String s);
String m3();
}
public class Derived extends Base implements IType {
public String m1() { return "Derived.m1()"; } // 覆寫
public String m3() { return "Derived.m3()"; } // 實作介面
}
public class Derived2 extends Derived {
public String m2(String s) { return "Derived2.m2(" + s + ")"; } // 覆寫
public String m4() { return "Derived2.m4()"; } // 新增
}
public class Separate implements IType { // 無繼承關係
public String m1() { return "Separate.m1()"; }
public String m2(String s) { return "Separate.m2(" + s + ")"; }
public String m3() { return "Separate.m3()"; }
}
當宣告 Derived2 d2 = new Derived2();,d2 擁有完整視窗,可見 m1()~m4()。
當以基類別引用指向同一物件:Base b = d2;,b 的視窗縮小,僅能看到 Base 定義的 m1()、m2(),但實際執行的程式碼仍由物件真實型別(Derived2)決定。
d2.m1(); // "Derived.m1()"
b.m1(); // "Derived.m1()" ← 同一物件,同一行為
d2.m3(); // "Derived.m3()"
b.m3(); // 編譯錯誤:Base 視窗看不到 m3()
IType 引用可同時指向 Derived2 與 Separate 兩個完全無繼承關係的物件:
IType it1 = new Derived2();
IType it2 = new Separate();
it1.m3(); // "Derived.m3()"
it2.m3(); // "Separate.m3()"
這展示了 Java 介面實現 多型別繼承 而避免了 C++ 多重繼承的菱形繼承問題:
傳統觀點常混淆「物件擁有的所有 public 方法」與「引用型別所暴露的介面」。
多態的本質在於 透過型別介面操作物件,將「做什麼」(契約)與「怎麼做」(實作)分離。介面型別決定舞伴與舞名,具體類別設計舞步。