Java 多態透過**運行時類型識別 (RTTI)** 實現。編譯期僅檢查引用類型是否存在該方法,實際調用的方法版本在運行期根據**物件真實類型**決定。
class Shape { void draw() { System.out.println("Shape"); } }
class Triangle extends Shape { void draw() { System.out.println("Triangle"); } }
public class Demo {
public static void main(String[] args) {
Shape s = new Triangle(); // 向上轉型
s.draw(); // 輸出: Triangle (動態綁定到 Triangle.draw)
}
}
class Parent {
private void show() { System.out.println("Parent"); }
}
class Child extends Parent {
public void show() { System.out.println("Child"); } // 這是新方法,非覆寫
}
Parent p = new Child();
p.show(); // 輸出: Parent (編譯期綁定 Parent.show)
原因: 私有方法對子類不可見,子類未繼承,自然無覆寫可言。
class Shape { int perimeter = 1; }
class Triangle extends Shape { int perimeter = 3; }
Shape s = new Triangle();
System.out.println(s.perimeter); // 1 (編譯期綁定 Shape.perimeter)
System.out.println(((Triangle)s).perimeter); // 3
Triangle t = new Triangle();
System.out.println(t.perimeter); // 3 (自身欄位遮蔽父類)
規則: 欄位訪問在編譯期根據**引用類型**綁定。最佳實踐: 欄位私有化,提供 getter/setter (方法具多態)。
class A { static void foo() { System.out.println("A"); } }
class B extends A { static void foo() { System.out.println("B"); } }
A a = new B();
a.foo(); // 輸出: A (隱藏而非覆寫)
B.foo(); // 輸出: B
靜態方法屬於類別,編譯期即綁定,無動態分派。
class Shape {
Shape() { draw(); } // 建構子調用實例方法
void draw() { System.out.println("Shape"); }
}
class Triangle extends Shape {
int value = 10;
Triangle() { System.out.println("Triangle init"); }
void draw() { System.out.println("Triangle: " + value); }
}
new Triangle();
// 輸出順序:
// Triangle: 0 ← 父建構子執行時,子類欄位尚未初始化 (預設 0)
// Triangle init
災難場景: 子類覆寫方法依賴尚未初始化的欄位 → NPE 或邏輯錯誤。
鐵律: **建構子中僅調用 private/final/static 方法** (不可被覆寫)。
Shape s = new Triangle();
s.draw(); // OK,父類有聲明
s.calcArea(); // 編譯錯誤,Shape 無此方法
((Triangle)s).calcArea(); // 需向下轉型
| 特性 | 實例方法 (覆寫) | 靜態方法 (隱藏) | 私有方法 | 欄位 |
|---|---|---|---|---|
| 多態性 | ✅ 動態綁定 | ❌ 編譯期綁定 | ❌ 不可見 | ❌ 編譯期綁定 |
| @Override 可用 | ✅ | ❌ | ❌ | ❌ |
| super. 可調用父版本 | ✅ | ✅ (父類名.方法) | ❌ | ✅ (super.欄位) |
Java 多態威力強大,但邊界清晰:**僅實例方法具動態分派**。深入理解「編譯期靜態綁定」與「運行期動態綁定」的分界線,避開私有/靜態/欄位/建構子四大陷阱,才能寫出健壯、可維護的物件導向代碼。