首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 多態深度解析:編譯時/運行時多態、重載/重寫、虛擬方法表與經典面試題

     編輯:Java 程式設計入門

多態核心定義

多態:同一介面,多種實現。Java 兩大多態:

  • 編譯時多態 (靜態綁定):方法重載。編譯器根據參數列表決定調用哪個方法。
  • 運行時多態 (動態綁定):方法重寫。JVM 根據物件實際類型決定調用哪個方法。

運行時多態三大必要條件

  1. 繼承/實現關係:子類 extends 父類 / implements 介面
  2. 方法重寫:子類重新定義父類方法 (同簽名、兼容返回型別、不拋更寬檢查異常)
  3. 父類引用指向子類物件:Parent p = new Child(); (向上轉型)

動態分派機制:虛擬方法表 (vtable)


// 簡化原理圖解
class Animal {
    void sound() { System.out.println("動物聲音"); }
    void breathe() { System.out.println("呼吸"); }
}

class Dog extends Animal {
    @Override void sound() { System.out.println("汪汪"); }
    void fetch() { System.out.println("撿球"); }
}

// JVM 內部為每個類建立 vtable (方法表)
// Animal vtable: [sound()->Animal.sound, breathe()->Animal.breathe]
// Dog vtable:    [sound()->Dog.sound,    breathe()->Animal.breathe] (繼承)

// 執行時:
Animal a = new Dog();
a.sound(); // 1. 取得 a 實際類型 Dog
           // 2. 查 Dog vtable 中 sound 入口
           // 3. 調用 Dog.sound() → 輸出 "汪汪"
// 注意:a.fetch() 編譯錯誤!Animal 引用不可見子類特有方法

方法重載 vs 重寫 完整對比

維度重載重寫
發生範圍同一類內父子類/介面實現類之間
方法名相同相同
參數列表必須不同 (類型/個數/順序)必須相同
返回類型可不同兼容 (子類型/基本型別相同)
拋出異常無限制子類異常 ⊆ 父類異常
存取修飾符無限制子類 ≥ 父類 (不能縮小)
綁定時機編譯期 (靜態)運行期 (動態)
多態類型編譯時多態運行時多態
@Override不可用強烈建議加 (編譯器驗證)

經典面試題:重載與重寫混合陷阱


class A {
    public String show(D obj) { return "A and D"; }
    public String show(A obj) { return "A and A"; }
}
class B extends A {
    public String show(B obj) { return "B and B"; }  // 重載 (參數不同)
    @Override public String show(A obj) { return "B and A"; } // 重寫
}
class C extends B {}
class D extends B {}

// 測試
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();

System.out.println(a1.show(b)); // ①
System.out.println(a1.show(c)); // ②
System.out.println(a1.show(d)); // ③
System.out.println(a2.show(b)); // ④
System.out.println(a2.show(c)); // ⑤
System.out.println(a2.show(d)); // ⑥
System.out.println(b.show(b));  // ⑦
System.out.println(b.show(c));  // ⑧
System.out.println(b.show(d));  // ⑨

答案與解析

呼叫輸出解析
① a1.show(b)A and Aa1 編譯類型 A,b 編譯類型 B。A 中無 show(B),向上找 show(A) → A.show(A)
② a1.show(c)A and A同 ①,C 編譯類型 C,A 無 show(C) → show(A)
③ a1.show(d)A and DA 有 show(D) 精確匹配 → A.show(D)
④ a2.show(b)B and Aa2 編譯類型 A,實際類型 B。A 無 show(B) → 找 show(A)。實際類型 B 重寫了 show(A) → B.show(A)
⑤ a2.show(c)B and A同 ④,C 編譯類型 C,A 無 show(C) → show(A) → B.show(A)
⑥ a2.show(d)A and DA 有 show(D) 精確匹配。雖然 a2 實際是 B,但 B 無 show(D),不涉及重寫 → A.show(D)
⑦ b.show(b)B and Bb 編譯類型 B,有 show(B) 精確匹配 → B.show(B)
⑧ b.show(c)B and Bb 編譯類型 B,無 show(C)。向上找父類 A 無 show(C)。第三優先級 this.show((super)C) → this.show(B) → B.show(B)
⑨ b.show(d)A and Db 編譯類型 B,無 show(D)。父類 A 有 show(D)。B 未重寫 show(D) → A.show(D)

方法調用優先級規則 (由高到低)

  1. this.show(O):當前類精確匹配參數類型
  2. super.show(O):父類精確匹配
  3. this.show((super)O):當前類匹配參數的父類型
  4. super.show((super)O):父類匹配參數的父類型

關鍵:編譯器根據引用變數編譯時類型的參數類型進行靜態綁定 (選擇重載);運行時根據物件實際類型進行動態綁定 (選擇重寫)。

多態的五大優勢

  1. 可替換性:子類可替換父類使用 (里氏替換原則)
  2. 可擴充性:新增子類不修改既有代碼 (開閉原則)
  3. 介面性:父類定義契約,子類實現細節
  4. 靈活性:運行時動態決定行為
  5. 簡化性:統一處理異構物件集合

實戰應用:策略模式 + Spring 依賴注入


// 1. 定義介面 (契約)
interface PaymentStrategy {
    void pay(BigDecimal amount);
}

// 2. 多種實現
@Component("alipay") class AlipayStrategy implements PaymentStrategy { ... }
@Component("wechat") class WechatStrategy implements PaymentStrategy { ... }
@Component("unionpay") class UnionPayStrategy implements PaymentStrategy { ... }

// 3. 上下文使用多態
@Service
class PaymentService {
    private final Map strategies;

    // Spring 自動注入所有實現到 Map (key 為 @Component value)
    public PaymentService(Map strategies) {
        this.strategies = strategies;
    }

    public void process(String channel, BigDecimal amount) {
        PaymentStrategy strategy = strategies.get(channel);
        if (strategy == null) throw new IllegalArgumentException("不支援管道: " + channel);
        strategy.pay(amount); // 多態調用
    }
}

// 4. 新增管道只需新增實現類,零修改既有代碼

常見誤區與避坑

  • 私有方法不可重寫:private 方法隱式 final,子類同名方法是新方法非重寫
  • 靜態方法不可重寫:static 方法屬於類,子類同名是隱藏非重寫,調用由編譯類型決定
  • 建構子不可重寫:建構子非成員方法,無多態
  • 欄位無多態:parent.field 總是訪問編譯類型的欄位 (隱藏非重寫)
  • final 方法不可重寫:編譯器直接內聯/靜態綁定
  • 預設方法衝突:實現多個介面有同名 default 方法需顯式重寫指定調用哪個
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved