J2SE 1.5 泛型的核心在於「參數化類型」。本文從最簡單的自訂泛型類入手,展示型別參數宣告、具體化實例化、型別安全保障,以及著名的「型別擦除」導致的運行時型別不安全現象。
package tigers;
class Generic1<T> { // 型別參數 T 為佔位符
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
public class GenericIdentify {
public static void main(String[] args) {
// 具體化為 String、Integer、Boolean
Generic1<String> gStr = new Generic1<>();
gStr.set("aaaaaaaaaaaaaaaaa");
Generic1<Integer> gInt = new Generic1<>();
gInt.set(1321423); // 自動裝箱 int → Integer
Generic1<Boolean> gBool = new Generic1<>();
gBool.set(true); // 自動裝箱 boolean → Boolean
System.out.println(gStr.get() + "\n" + gInt.get() + "\n" + gBool.get());
// 型別擦除示範:透過原始類型繞過編譯期檢查
((Generic1) gInt).set("bbbbbbbbbbbbb"); // 未受檢轉型警告
System.out.println(gInt.get()); // 運行時無誤,但破壞型別安全
}
}
C:\tiger> javac -d c:\tiger\cls\ c:\tiger\src\*.java
Note: c:\tiger\src\GenericIdentify.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
C:\tiger> java -classpath c:\tiger\cls tigers.GenericIdentify
aaaaaaaaaaaaaaaaa
1321423
true
bbbbbbbbbbbbb
<T>T (Type)、E (Element)、K/V (Key/Value)、N (Number)。<T extends Comparable<T>>。Generic1<int> 編譯錯誤,必須用包裝類 Integer。Generic1<String> 和 Generic1<Integer> 編譯後**同一個 Class 物件**。((Generic1) gInt).set("string"); // 原始類型 Generic1,set 參數為 Object
編譯器發出 unchecked 警告,但允許通過。運行時因型別擦除,set(Object) 正常執行,導致 Generic1<Integer> 實例持有 String 物件。後續 get() 返回 String,若呼叫端當 Integer 用會拋 ClassCastException。
gInt.set(1321423); // int → Integer 自動裝箱
int val = gInt.get(); // Integer → int 自動拆箱
簡化基本型別與包裝類互轉,但需注意 null 拆箱拋 NPE、自動裝箱產生物件影響效能。
@SuppressWarnings("unchecked") 只在確定安全處局部抑制。new T[10] 編譯錯誤,new Generic1[10] 原始類型陣列不安全,改用 List<T>。extends (Producer) 用於讀取,super (Consumer) 用於寫入。泛型以編譯期型別參數化換取型別安全與程式碼復用,但受限於型別擦除,運行時型別資訊丟失。理解擦除機制、原始類型風險與自動裝箱互動,是駕馭 Java 泛型的關鍵。