多態是面向對象三大特性之王,也是 Java 動態分派的基石。本文通過 4 個經典代碼實例,結合 invokespecial/invokevirtual 字節碼指令,徹底講透:構造器鏈中的多態行為、字段無多態、靜態方法隱藏、super 調用語義、類加載初始化順序、向上轉型與強制轉型實戰、多態在策略/模板方法模式中的應用,助你構建「編譯期靜態、運行期動態」的立體認知。
class Parent {
String s = "s parent";
Parent() { System.out.println("Parent 構造器"); fun(12); f(); p(); }
void p() { System.out.println("P"); }
static void f() { System.out.println("Parent static f()"); }
private void fun(int i) { System.out.println("Parent fun()"); }
}
class Child extends Parent {
String s = "s Child";
Child() { System.out.println("Child 構造器"); fun(12); f(); }
static void f() { System.out.println("Child static f()"); }
public void fun(int i) { System.out.println("Child fun()"); }
}
public class Demo {
public static void main(String[] args) {
Parent t = new Child(); // 向上轉型
System.out.println(t.s); // 字段無多態 → "s parent"
}
}
Parent 構造器 Parent fun() ← invokespecial:private 方法靜態綁定 Parent static f() ← invokespecial:static 方法靜態綁定 P ← invokevirtual:實例方法動態分派 → 但 Child 未覆蓋 p() Child 構造器 Child fun() ← invokevirtual:fun() 被覆蓋,動態分派到 Child Child static f() ← invokespecial:static 隱藏,編譯期綁定 Child s parent ← 字段訪問無多態,編譯期綁定引用類型 Parent
invokespecial 靜態綁定 (前期綁定)invokevirtual 動態分派 (後期綁定),基於對象實際類型t.s 永遠是 Parent.sParent.f() vs Child.f()class SuperParent { SuperParent() { System.out.println("SuperParent"); f(); } void f() { System.out.println("SuperParent"); } }
class Parent extends SuperParent { Parent() { System.out.println("Parent"); f(); super.f(); } void f() { System.out.println("Parent"); } }
class Child extends Parent { Child() { System.out.println("Child"); f(); super.f(); } void f() { System.out.println("Child"); } }
new Child();
// 輸出:
// SuperParent → Child (動態分派 f)
// Parent → Child → SuperParent (super.f 靜態綁定父類)
// Child → Child → Parent
super.f() 編譯期綁定父類實現 (invokespecial),this.f() 運行期動態分派 (invokevirtual)。
class SuperParent {
static { System.out.println("SuperParent static"); }
{ System.out.println("SuperParent instance block"); }
SuperParent() { System.out.println("SuperParent constructor"); }
}
class Parent extends SuperParent {
static { System.out.println("Parent static"); }
{ System.out.println("Parent instance block"); }
Parent() { System.out.println("Parent constructor"); }
}
class Child extends Parent {
static { System.out.println("Child static"); }
{ System.out.println("Child instance block"); }
Child() { System.out.println("Child constructor"); }
}
// new Child() 輸出:
// SuperParent static → Parent static → Child static
// SuperParent instance block → SuperParent constructor
// Parent instance block → Parent constructor
// Child instance block → Child constructor
鐵律:父類靜態 → 子類靜態 → 父類實例塊/構造器 → 子類實例塊/構造器。靜態只執行一次,實例塊每 new 執行一次。
abstract class Shape {
public abstract double area();
public abstract double perimeter();
// 模板方法:通用邏輯固化
public void printInfo() {
System.out.printf("面積: %.2f, 周長: %.2f%n", area(), perimeter());
}
}
class Circle extends Shape {
private double r;
Circle(double r) { this.r = r; }
public double area() { return Math.PI * r * r; }
public double perimeter() { return 2 * Math.PI * r; }
}
class Rect extends Shape {
private double w, h;
Rect(double w, double h) { this.w = w; this.h = h; }
public double area() { return w * h; }
public double perimeter() { return 2 * (w + h); }
}
// 向上轉型參數:消除類型耦合,擴展新圖形無需修改此方法 (OCP)
public static void print(Shape s) { s.printInfo(); }
// 向上轉型返回:工廠方法模式
public static Shape randomShape() {
return Math.random() > 0.5 ? new Circle(5) : new Rect(3, 4);
}
public static void main(String[] args) {
print(randomShape()); // 運行期動態分派 area()/perimeter()
}
| 優點 | 缺點/限制 | 最佳實踐 |
|---|---|---|
| 消除類型耦合,滿足開閉原則 (OCP) | 無法訪問子類特有成員/方法 | 向下轉型前 instanceof 守衛 / 訪問者模式 | 運行期靈活替換實現 (策略/插件) | 調試困難:靜態分析無法確定具體調用鏈 | 日誌打印實際類型 / IDE 運行期類型追蹤 | 代碼復用:模板方法固化骨架 | 構造器調用可覆蓋方法導致未初始化字段訪問 | 構造器只調用 final/private/static 方法 | 集合統一管理異構對象 | 性能微損:動態分派間接跳轉 (JIT 內聯優化後可忽略) | 熱點路徑考慮 final/sealed class 禁止覆蓋 |
Shape s = randomShape();
if (s instanceof Circle) { // Java 14+ 模式匹配:if (s instanceof Circle c)
Circle c = (Circle) s;
System.out.println("半徑: " + c.getR());
} else if (s instanceof Rect) {
Rect r = (Rect) s;
System.out.println("寬: " + r.getW());
}
// 避免 ClassCastException:永遠 instanceof 後再 cast
Java 多態本質是 invokevirtual 動態分派:運行期根據對象實際類型選擇方法實現。構造器鏈中動態分派是「多態陷阱」高發區,字段/靜態方法無多態。掌握 invokespecial vs invokevirtual、初始化順序、向上轉型消除耦合、向下轉型安全守衛、模板方法/策略模式落地,即掌握了面向對象設計的核心槓桿。下一篇我們將系統講解 Java 繼承機制與組合復用原則對比。