Java 泛型深入底層原理解析:類型擦除與橋方法的真相
一、Java中的偽泛型
Java 從 JDK 1.5 引入泛型之后,大大提升了代碼的類型安全性與可讀性。但泛型的底層實現并不像 C++ 的模板機制那樣是“真正的泛型”,Java 的泛型是偽泛型,在編譯后會進行類型擦除,這帶來了很多容易忽略的陷阱。
這篇文章將主要介紹Java 泛型是如何通過擦除實現,泛型在編譯期與運行期發生了什么,為什么會出現橋方法,以及通過反射獲取泛型信息
二、泛型基本語法
public class Box<T> {private T value;public void set(T value) { this.value = value; }public T get() { return value; }
}
我們可以通過如下方式使用它:
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
// 編譯器知道 stringBox 中只能放 String
看似“類型安全”,但實際運行時情況并不是這樣。因為運行時,由于類型擦除機制,泛型的類型信息被移除,導致實際運行中并不能真正限制類型,看下面這段代碼,因為編譯時期進行了類型擦除,因此反射時jvm并不知道list
中只允許添加String
List<String> list = new ArrayList<>();
list.add("hello");// 通過反射繞過泛型限制
Method addMethod = list.getClass().getMethod("add", Object.class);
addMethod.invoke(list, 123); // 添加了一個 IntegerSystem.out.println(list); // 輸出: [hello, 123]
三、類型擦除:T 到底去哪了?
Java 的泛型是編譯期語法糖,在編譯過程中,T 會被替換成其限定類型(上限)。如果沒有顯式指定,則默認是 Object
。看下面的例子
public class Box<T> {T value;void set(T value) { this.value = value; }T get() { return value; }
}
編譯后實際變成了:
public class Box {Object value;void set(Object value) { this.value = value; }Object get() { return value; }
}
這就意味著泛型信息在運行時根本就不存在,這就是類型擦除。
四、類型擦除帶來的限制與問題
1. 泛型不能用于基本類型
Box<int> box;
因為泛型最終會被擦除為 Object
,而基本類型不能直接賦值給 Object
,需要裝箱(autoboxing)。
2. 泛型不能創建數組
T[] arr = new T[10]; // 編譯錯誤
擦除后不知道 T 是什么類型,無法分配合適的數組類型。
3. 泛型類無法通過 instanceof
判斷類型參數
if (box instanceof Box<String>) {} // 編譯錯誤
因為泛型信息在運行期被擦除了,JVM 無法判斷類型。
五、橋方法:擦除下的多態陷阱
當子類重寫帶泛型的方法時,由于擦除后簽名可能不同(子類重寫父類方法后一般要求參數類型和數量不變,因為底層要生成一個方法簽名,這部分內容涉及到多態,動態鏈接的知識),為了保證運行時的多態性,編譯器會自動生成橋方法。例如下面這段代碼
class Parent<T> {T get() { return null; }
}class Child extends Parent<String> {@OverrideString get() { return "hello"; }
}
在實際編譯后為
class Child extends Parent {// 橋方法Object get() {return get(); // 調用下面的真實方法}// 實際的 get 方法String get() {return "hello";}
}
橋方法保證了子類方法可以在擦除后繼續覆蓋父類的方法,維護多態特性。
六、反射獲取泛型信息
雖然泛型信息在運行時被擦除,但某些泛型信息仍保留在 class 文件的元數據中,可以通過反射讀取。這部分簡單介紹一下,如下代碼
class GenericHolder<T> {}class StringHolder extends GenericHolder<String> {}public static void main(String[] args) {Type superClass = StringHolder.class.getGenericSuperclass();System.out.println(superClass);
}
總結
Java泛型通過類型擦除機制在編譯時期提供類型安全,但運行時類型信息會被擦除,因此在反射、數組等場景時需要格外注意