Java 泛型採用類型擦除:編譯期檢查類型安全,運行期將泛型參數替換為邊界類型(無界則為 Object),再插入隱式強轉。這導致泛型方法簽名在運行期可能與父類/接口方法簽名不一致,破壞多態分派。
// 父類:泛型參數 String
public class ParentString<T> {
T src;
public void setSrc(T src) { this.src = src; }
}
// 子類:固化泛型為 String
public class ChildString extends ParentString<String> {
String s = "childString";
@Override
public void setSrc(String src) { // 參數類型從 T 變為 String
src = s;
}
}
編譯器擦除後,父類方法簽名變為 setSrc(Object),子類重寫方法簽名為 setSrc(String)。參數類型不同 → 不是重寫,而是重載。這將導致多態失效:
ChildString child = new ChildString();
ParentString<String> parent = child; // 多態引用
parent.setSrc("aaa"); // 調用的是 ParentString.setSrc(Object)!
// 子類的 setSrc(String) 根本不會被調用
為修復多態,編譯器在 ChildString 中自動合成一個橋接方法:
// 編譯器生成(反編譯可見)
public void setSrc(Object src) { // 簽名與父類擦除後一致
setSrc((String) src); // 委派給真正的重寫方法
}
調用鏈變為:
parent.setSrc("aaa")
→ ChildString.setSrc(Object) [橋接方法]
→ ChildString.setSrc(String) [真實邏輯]
| 特徵 | 說明 |
|---|---|
| 方法名 | 與被重寫方法相同 |
| 參數類型 | 父類擦除後的類型(通常是 Object 或邊界類型) |
| 返回類型 | 父類擦除後的返回類型 |
| 方法體 | 僅做類型轉換 + 委派調用子類真實方法 |
| 修飾符 | public + synthetic(標記為編譯器生成) |
| 註解 | @Bridge(內部標記) |
class Animal {}
class Cat extends Animal {}
interface Factory { Animal create(); }
class CatFactory implements Factory {
@Override public Cat create() { return new Cat(); } // 返回類型收窄
}
// 擦除後 Factory.create() 簽名為 Animal create()
// 編譯器在 CatFactory 生成橋接方法:
// public Animal create() { return create(); } // 協變返回自動兼容
// 1. 編譯上述代碼
// 2. javap -c -p ChildString.class
// 輸出片段:
public void setSrc(java.lang.String); // 真實重寫
public volatile void setSrc(java.lang.Object); // 橋接方法 (ACC_BRIDGE | ACC_SYNTHETIC)
Code:
0: aload_0
1: aload_1
2: checkcast #String
5: invokevirtual #setSrc(String)
8: return
class A<T extends A<T>> 複雜層次易產生多個橋接方法衝突 → 簡化泛型層次Method.invoke 可能調到橋接方法 → 用 method.isBridge() 過濾,或直接調用具體實現類方法isBridge() 方法ACC_BRIDGE | ACC_SYNTHETIC 標記,可通過反射識別