一、泛型是什么?
1. 定義:
泛型允許你在定義類、接口或方法時使用類型參數(Type Parameter)。在使用時(如聲明變量、創建實例時),再用具體的類型實參(Type Argument)?替換這個參數。它就像是方法的形參和實參,但操作的對象是類型本身。
2. 核心目的:
類型安全(Type Safety):在編譯期就能檢查類型是否正確,將運行時錯誤(ClassCastException)轉變為編譯期錯誤。
// 沒有泛型:編譯通過,運行時報 ClassCastException List list = new ArrayList(); list.add("Hello"); Integer num = (Integer) list.get(0); // 運行時錯誤!// 有泛型:編譯期直接報錯,無法通過編譯 List<String> list = new ArrayList<>(); list.add("Hello"); Integer num = list.get(0); // 編譯錯誤:不兼容的類型
消除強制類型轉換(Eliminate Casts):代碼更簡潔、清晰。
// 沒有泛型 String str = (String) list.get(0);// 有泛型 String str = list.get(0); // 自動知道是String,無需強轉
二、泛型擦除(Type Erasure)—— 泛型的實現原理
這是 Java 泛型的核心機制,也是很多限制的根源。
1. 是什么?
Java 的泛型是在編譯器層面實現的,而不是在運行時。在編譯后,所有的泛型類型信息都會被移除(擦除)。編譯器在生成字節碼時:
將泛型類型參數替換為它的邊界(Bound)(如?
T extends Number
?則替換為?Number
)。如果類型參數是無邊界的(如?
<T>
),則替換為?Object
。隨之插入必要的強制類型轉換,以保持類型安全。
2. 例子:
// 源代碼(編譯前)
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");
String value = stringBox.get(); // 無需強轉// 編譯后(概念上,字節碼級別)
public class Box { // T 被擦除private Object value; // T 被替換為 Objectpublic void set(Object value) { this.value = value; }public Object get() { return value; } // 返回Object
}Box stringBox = new Box(); // raw type
stringBox.set("Hello");
String value = (String) stringBox.get(); // 編譯器插入了強轉!
3. 帶來的影響與限制:
不能使用基本類型:如?
List<int>
?是錯誤的,必須用?List<Integer>
。因為擦除后是?Object
,而?Object
?不能持有?int
。instanceof 和 getClass():運行時無法檢測泛型類型。
List<String> list = new ArrayList<>(); System.out.println(list instanceof List<String>); // 編譯錯誤 System.out.println(list instanceof List); // 正確,但只能檢查到是List,不是List<String>
不能創建泛型數組:
new T[]
?或?new List<String>[]
?都是錯誤的。因為數組需要在運行時知道其確切的元素類型來保證類型安全,而擦除破壞了這個機制。不能實例化類型參數:
new T()
?是錯誤的,因為擦除后是?new Object()
,這通常不是你想要的。
三、橋接方法(Bridge Method)—— 保護多態
橋接方法是編譯器為了解決類型擦除與多態沖突而自動生成的方法。
場景:?當一個類繼承或實現了一個泛型類/接口,并重寫了其中的泛型方法時。
例子:
// 泛型接口
public interface Comparable<T> {int compareTo(T other);
}// 實現類
public class String implements Comparable<String> {// 我們重寫的方法簽名:int compareTo(String other)@Overridepublic int compareTo(String other) { ... }
}
由于類型擦除,父接口?Comparable
?中的方法在字節碼層面變成了?int compareTo(Object other)
。這導致子類?String
?實際上有兩個方法:
int compareTo(String other)
?(我們自己寫的)int compareTo(Object other)
?(編譯器生成的橋接方法)
橋接方法內部做了什么?
// 編譯器生成的橋接方法(概念上)
public int compareTo(Object other) {// 在橋接方法中,進行類型檢查和安全地向下轉型return compareTo((String) other); // 調用我們重寫的那個具體類型的方法
}
橋接方法確保了即使在類型擦除后,Java的多態機制(父類引用調用子類方法)也能正常工作,同時保證了類型安全。
四、泛型繼承和通配符(Wildcards):extends
?&?super
這是泛型中最難理解但最強大的部分,通常用?PECS(Producer-Extends, Consumer-Super)?原則來概括。
1. 泛型不變性(Invariance)
首先,理解這一點至關重要:Box<String>
?和?Box<Object>
?沒有繼承關系,即使?String
?是?Object
?的子類。
Box<Object> box = new Box<String>(); // 編譯錯誤!不兼容的類型
這種特性稱為不變性(Invariance)。它保證了類型安全。如果上面成立,你就可以?box.set(new Integer(100))
,從而把一個?Integer
?放進一個聲明為?String
?的盒子里。
2. 通配符??
為了解決需要泛型協變的需求,引入了通配符??
。
3. 上界通配符?? extends T
?(Producer)
含義:表示“未知的某種類型,但它是?
T
?或?T
?的子類”。用途:當你主要從泛型結構中讀取數據(Producer)?時使用。
規則:你可以安全地從中讀取(賦值給?
T
?或父類引用),但不能向其寫入(除了?null
)。因為編譯器不知道具體是哪種子類,寫入可能破壞類型安全。List<? extends Number> numbers = new ArrayList<Integer>(); // 協變,允許 Number num = numbers.get(0); // OK, 可以讀取為Number numbers.add(new Integer(100)); // 編譯錯誤!不能寫入
4. 下界通配符?? super T
?(Consumer)
含義:表示“未知的某種類型,但它是?
T
?或?T
?的父類”。用途:當你主要向泛型結構中寫入數據(Consumer)?時使用。
規則:你可以安全地向其寫入?
T
?或?T
?的子類對象,但讀取出來只能賦值給?Object
?引用。因為編譯器只知道容器里是?T
?的父類,無法確定具體類型。List<? super Integer> list = new ArrayList<Number>(); // 逆變,允許 list.add(new Integer(123)); // OK, 可以寫入Integer及其子類 Integer num = list.get(0); // 編譯錯誤!無法安全讀取 Object obj = list.get(0); // OK, 只能讀取為Object
5. PECS 原則總結
Producer-Extends (P-E):如果你需要一個提供(生產)?
T
?對象的泛型結構(主要調用?get()
),使用?<? extends T>
。例如:Collection<? extends T>.get()
。Consumer-Super (C-S):如果你需要一個接收(消費)?
T
?對象的泛型結構(主要調用?add()
),使用?<? super T>
。例如:Collection<? super T>.add(T)
。既生產又消費:如果你既要讀又要寫,那就不要用通配符,直接用確切的類型,如?
<T>
。
經典應用:Collections.copy()
public static <T> void copy(List<? super T> dest, List<? extends T> src) {// dest 是消費者 (Consumer),消費T對象,所以用 ? super T// src 是生產者 (Producer),生產T對象,所以用 ? extends Tfor (int i =0; i < src.size(); i++) {dest.set(i, src.get(i));}
}
五、常見問題總結
Q:“詳細講講Java的泛型。”
A:
“Java泛型的核心目的是提供編譯時類型安全和消除強制類型轉換。它的實現機制是類型擦除,即在編譯后泛型信息會被移除,類型參數會被替換為它的邊界或Object,并由編譯器自動插入強制轉換。
類型擦除帶來了一些限制,比如不能使用基本類型、不能進行泛型的instanceof檢查、不能創建泛型數組等。為了解決擦除與多態的沖突,編譯器會生成橋接方法,它在子類重寫泛型方法時,負責進行類型檢查和安全轉型,從而保證多態性。
泛型具有不變性,Box<String>
?不是?Box<Object>
?的子類。為了更靈活的API設計,引入了通配符??
?和?PECS原則:
? extends T
?用于生產者,表示可以安全讀取,但不能寫入。? super T
?用于消費者,表示可以安全寫入,但讀取受限。