?Java 泛型本質是參數化類型,可以用在類、接口和方法的創建中。
1 “擦除式”泛型
Java的“擦除式”的泛型實現一直受到開發者的詬病。
“擦除式”的實現幾乎只需要在Javac編譯器上做出改進即可,不要改動字節碼、虛擬機,也保證了以前沒有使用泛型的庫可以之間運行在java 5.0 之上。但是這個帶來了以下弊端:
- 例如對于List<String> 和 List<Integer> ,在運行時由于擦除了,所以這兩個都變成了List,因此它們在運行中是同一類型。(而對于C#的泛型來說,無論在源碼中、編譯后及運行時它們始終是不同的類型)。這導致在運行時,獲取不到類型信息。
- “擦除式”的實現,是在元素被賦值時編譯器自動插入類型檢查指令,訪問元素時,自動插入類型強制轉換指令。這樣頻繁的類型檢查及轉換,導致Java的泛型性能差于C#的泛型。
public class EraseGeneric {private static class Holder<T> {T t;public T getT() {return t;}public void setT(T t) {this.t = t;}}public static void main(String[] args) {Holder<String> holder = new Holder<>();holder.setT("hello");String str = holder.getT();}}
圖 Holder類被編譯后的字節碼片段
圖 main 方法中,對于Holder類型的賦值及訪問操作字節碼
1.1 擦除的補償
如果要在運行時獲取類型信息,那么可以通過引入類型標簽來對擦除進行補償。
public class TypeTagGeneric {private static class User {public User() {}}private static <T> void fun(Class<T> kind) throws InstantiationException, IllegalAccessException {T t = kind.newInstance();System.out.println(t);}public static void main(String[] args) throws InstantiationException, IllegalAccessException {fun(User.class);int[] array1 = new int[10];String[] array2 = new String[10];}}
2 協變與逆變
A 類型是B的父類型,對于某個構造器,構造出的復雜類型A`與B`。
協變 | A`仍然是B`的父類型。比如Java中的數組,A[] 仍是B[]的父類型。 |
逆變 | B`是A`的父類型。 |
抗變 | A`與B`沒有任何繼承關系。例如List<A> 與List<B>沒有任何繼承關系。 |
表 協變、逆變與抗變
2.1 數組與泛型
T[] t = new T[10]; 這個代碼是錯誤的,Java中規定不能創建泛型數組。
因為Java 在運行時,無法獲取泛型的類型信息,因為在創建數組時,也就無法獲取到泛型參數所表示的確切類型。
2.1.1 數組的類型
Java中數組的種類有兩種:
- 基礎類型的數組:[ + 開頭大寫字母。
int[] : [I
- 引用類型的數組:[ + L + 類型。
String[] array2 : [Ljava/lang/String
public class ArrayGeneric {private static class Fruit {}private static class Apple extends Fruit {}public static void main(String[] args) {Fruit[] fruits = new Fruit[10];Apple[] apples = new Apple[10];System.out.println(fruits instanceof Fruit[]); // trueSystem.out.println(fruits instanceof Apple[]); // falseSystem.out.println(apples instanceof Fruit[]); // trueSystem.out.println(apples instanceof Apple[]); // truefruits = apples;
// apples = fruits; // 編譯錯誤System.out.println(fruits.getClass().getSuperclass()); // class java.lang.ObjectSystem.out.println(apples.getClass().getSuperclass()); // class java.lang.Object
// getSuperclass() 方法:如果此 Class 表示 Object 類、一個接口、一個基本類型或 void,則返回 null。
// 如果此對象表示一個數組類,則返回表示該 Object 類的 Class 對象。否則返回該類的超類。}}
2.2 通配符
泛型中的通配符用于在兩個類型之間建立某種類型的向上轉型關系。
協變 | ? extends T, 例如List<? extends Fruit> list。確定了元素類型的父類為Fruit,但不能確定其確切類型,因此不能往該容器添加新的元素(只能添加null)。但是可以從容器中提取元素,類型為Fruit。 |
逆變 | ? super T,例如List<? super Apple> list,確定了元素為Apple的父類,因為可以往容器中添加元素,但不能提取元素。 |
表 通配符的協變與逆變
public class CovarianceAndContravariance {private static class Fruit {}private static class Apple extends Fruit {}private static <T extends Apple> void setItem(List<? super Apple> list, T item) {list.add(item);}private static Fruit getItem(List<? extends Fruit> list,int pos) {return list.get(pos);}public static void main(String[] args) {List<? super Apple> list = new ArrayList<>();setItem(list,new Apple());List<? extends Fruit> list2 = Arrays.asList(new Fruit(),new Apple());Fruit item = getItem(list2, 0);}}
2.2.1 無界通配符
無界通配符,例如List<?>, 其相當于List<? extends Object>,但不等價于List(相當于List<Object>)。其有兩個作用:
- 告訴編譯器,我用了泛型,只是還沒確定哪個類型;
- 用于捕獲類型。
public class CaptureGeneric {private static class Holder<T> {}private static <T> void fun1(Holder<T> holder) {System.out.println(holder);}private static void fun2(Holder<?> holder) {fun1(holder);}public static void main(String[] args) {Holder holder = new Holder(); // 原生類型fun1(holder); // 警告,Unchecked assignment:fun2(holder); // 不會警告,無邊界通配符將不會這個原生類型的類型參數(Object)}}