Java 泛型提供編譯期型別檢查,同時保留與泛型出現前位元組碼的相容性。編譯器會抹除型別引數,在需要的位置插入轉型,並可能產生橋接方法,以在抹除後保留覆寫行為。因此 List<String> 與 List<Integer> 在執行時具有相同的類別,而泛型型別引數無法可靠地從物件中復原。
不變性與界限 #
泛型類別是不變的:List<Integer> 不是 List<Number>,因為後者會允許插入 Double。萬用字元在 API 邊界表達變異性。
static double sum(List<? extends Number> values) {
return values.stream().mapToDouble(Number::doubleValue).sum();
}
static void addDefaults(List<? super Integer> destination) {
destination.add(0);
}從呼叫端讀取的生產者使用 extends,呼叫端寫入的消費者使用 super。List<? extends Number> 能產生 Number 值,但不接受任何非 null 值;List<? super Integer> 接受 Integer 值,但只能產生 Object。「唯讀」與「唯寫」只是簡寫,不是能力規則:兩種清單仍可查詢、清除,或在實作允許時接收 null。
捕獲與推斷 #
每個萬用字元都表示一個未知的捕獲型別。萬用字元容器無法表示兩個各自使用萬用字元的值具有相同型別,因此關係重要時請使用泛型輔助方法:
static void reverse(List<?> list) { reverseCaptured(list); }
private static <T> void reverseCaptured(List<T> list) {
Collections.reverse(list);
}方法推斷會結合引數限制、目標型別與宣告的界限。菱形運算子會為建構函式呼叫推斷型別引數;它不會讓原始型別變得安全。界限可以要求能力:<T extends Comparable<? super T>> T max(List<? extends T> values)。交集界限可以包含一個類別,後接多個介面。
抹除下的建構 #
new T()、new T[n] 與 T.class 都不合法,因為抹除不會保留執行時所需的建構函式、元件型別或類別常值。請改傳入工廠:
static <T> T create(Supplier<? extends T> factory) {
return factory.get();
}若要整合反射,請接受 Class<T> 並處理其已檢查反射例外;但呼叫端負責建構時,優先使用 Supplier<T>。泛型陣列仍受限制,因為陣列具體化且具協變性,而泛型型別會被抹除且具不變性。
評論
在 GitHub 上閱讀或評論(發表評論需要登入)