Java 泛型 泛型數組
@author ixenos
- 先給結論
- 不能(直接)創建泛型數組
- 泛型數組實際的運行時對象數組只能是原始類型( T[]為Object[],Pair<T>[]為Pair[] ),而實際的運行時數組對象可能是T類型( 雖然運行時會擦除成原始類型 )
- 一般解決方案:(泛型數組包裝器):使用ArrayList收集泛型數組對象的對象元素,如ArrayList<T>、ArrayList<Pair<String>>
- 將獲得數組的行為,以及由泛型提供的編譯期的類型安全
- 直接創建泛型數組不能通過編譯,而轉型對象數組通過編譯但是不能在JVM運行
-
public class ArrayOfGeneric{ static Generic<Integer>[] gia; @SupperssWarnings("unchecked") public static void main(String[] args){ gia = (Generic<Integer>[])new Generic[100]; // 通過類型轉換匿名對象 //! gia[0] = new Object(); //編譯不通過,不能(直接)創建泛型數組實例 } }
- 問題在於數組將跟蹤他們的實際類型,而這個類型是在數組被創建時確定的,因此,即使gia已經被轉型為Generic<Integer>[],但這個信息只存在於編譯期(並且如果沒有@SuppressWarning("unchecked")注解,將得到這個轉型的警告)。在運行時,它仍舊是Object數組
- 因此,成功創建泛型數組的唯一方式就是創建一個被擦出類型的新數組,然后對其轉型(而且是在運行時轉型)
- 直接對整個數組強制轉型,在編譯時依舊會被擦除掉類型!所以應該在運行時轉型,而這時最好的辦法就是使用一個泛型數組包裝器,維護一個原始類型的數組,通過數組入口方法進行元素編譯期的類型安全檢測(對應返回值)和強制類型轉換(對於運行時不重要),從而保證類型安全。
-
- 對整個數組強制轉型的例子(錯誤方法)
-
public class GenericArray<T> { private T[] array; @SupperessWarning("unchecked") public GenericArray(int sz) { array = (T[]) new Object[sz]; } public void put(int index, T item) { array[index] = item; } public T get(int index) { return array[index]; } public T[] rep() { return array; } //應該在運行時出口做文章 public static void main (String[] args){ GenericArray<Integer> gai = new GenericArray<Integer>(10); // Integer[] ia = gai.rep(); //ClassCastException Object[] oa = gai.rep(); //只能返回對象數組類型為Object[]
- 實際的運行時對象數組是Object[],而實際的運行時數組對象可能是T類型。
-
- 因此,應該在運行時,數組對象的出口做轉型輸出,入口方法在編譯期已實現類型安全,所以出口方法可以放心強制類型轉換,保證成功。如下
-
public class GenericArray2<T> { private Object[] array; //維護Object[]類型數組 @SupperessWarning("unchecked") public GenericArray2(int sz) { array = new Object[sz]; } public void put(int index, T item) { array[index] = item; } public T get(int index) { return (T)array[index]; }//數組對象出口強轉 public T[] rep() { return (T[])array; } //運行時無論怎樣都是Object[]類型 public static void main (String[] args){ GenericArray<Integer> gai = new GenericArray<Integer>(10); // Integer[] ia = gai.rep(); //依舊ClassCastException Object[] oa = gai.rep(); //只能返回對象數組類型為Object[] gai.put(0,11); System.out.println(gai.get(0)); // 11 ,出口成功轉型 } }
-
- 因此,應該在運行時,數組對象的出口做轉型輸出,入口方法在編譯期已實現類型安全,所以出口方法可以放心強制類型轉換,保證成功。如下
- 通過反射在運行時構出實際類型為type[]的對象數組,避免了類型擦除,從而轉換成功,無ClassCastException
import java.lang.reflect.*; public class GenericArrayWithTypeToken<T> { private T[] array; @SuppressWarning("unchecked") public GenericArrayWithTypeToken(Class<T> type, int sz) { array = (T[]) Array.newInstance(type, sz);//通過反射在運行時構出實際類型為type[]的對象數組,避免了類型擦除,從而轉換成功,無ClassCastException } public void put(int index, T item){ array[index] = item; } public T get(int index) { return array[index]; } public T[] rep() { return array; } //能成功返回了~ public static void main(String[] args) { GenericArrayWithTypeToken<Integer> gawtt = new GenericArrayWithTypeToken<>(Integer.class, 10); Integer[] ia = gawtt.rep(); //能成功返回了! } }