首頁 > Java > java教程 > Java泛型總結(二)-泛型與陣列

Java泛型總結(二)-泛型與陣列

黄舟
發布: 2017-03-22 10:23:43
原創
1423 人瀏覽過

陣列與泛型的關係還是有點複雜的,Java 中不允許直接建立泛型陣列。本文分析了其中原因並且總結了一些創建泛型數組的方式。具有很好的參考價值。下面跟著小編一起來看下吧

簡介

上一篇文章介紹了泛型的基本用法以及類型擦除的問題,現在來看看泛型和數組的關係。陣列比起Java 類別函式庫中的容器類別是比較特殊的,主要體現在三個方面:

  • 陣列建立後大小便固定,但效率更高

  • 陣列能追蹤它內部保存的元素的具體類型,插入的元素類型會在編譯期間得到檢查

  • 陣列可以持有原始型別( int,float等),不過有了自動裝箱,容器類別看上去也能持有原始型別了

那麼當數組遇到泛型會怎樣? 能否創建泛型數組呢?這是這篇文章的主要內容。

這個系列的另外兩篇文章:

  • Java 泛型總結(一):基本用法與型別擦除

  • Java 泛型總結(三):通配符的使用

#泛型陣列

如何建立泛型數組

如果有一個類別如下:

 class Generic<T> {
 
}
登入後複製

如果要建立一個泛型數組,應該是這樣:Generic<Integer&gt ; ga = new Generic<Integer>[]  不過行程式碼會報錯,也就是說不能直接建立泛型陣列。

那麼如果要使用泛型陣列怎麼辦?一種方案是使用 ArrayList,例如下面的範例:

public class ListOfGenerics<T> {
 private List<T> array = new ArrayList<T>();
 public void add(T item) { array.add(item); }
 public T get(int index) { return array.get(index); }
}
登入後複製

如何建立真正的泛型陣列呢?我們不能直接創建,但可以定義泛型數組的引用。例如:

public class ArrayOfGenericReference {
 static Generic<Integer>[] gia;
}
登入後複製

gia 是一個指向泛型陣列的引用,這段程式碼可以透過編譯。但是,我們並不能創建這個確切類型的數組,也就是不能使用new Generic<Integer>[]  具體參見下面的例子:

public class ArrayOfGeneric {
 static final int SIZE = 100;
 static Generic<Integer>[] gia;
 @SuppressWarnings("unchecked")
 public static void main(String[] args) {
 // Compiles; produces ClassCastException:
 //! gia = (Generic<Integer>[])new Object[SIZE];
 // Runtime type is the raw (erased) type:
 gia = (Generic<Integer>[])new Generic[SIZE];
 System.out.println(gia.getClass().getSimpleName());
 gia[0] = new Generic<Integer>();
 //! gia[1] = new Object(); // Compile-time error
 // Discovers type mismatch at compile time:
 //! gia[2] = new Generic<Double>();
 Generic<Integer> g = gia[0];
 }
} /*输出:
Generic[]
*///:~
登入後複製

數組能追蹤元素的實際類型,這個類型是在數組創建的時候建立的。上面被註解掉的一行程式碼: gia = (Generic<Integer>[])new Object[SIZE],陣列在建立的時候是一個Object 數組,如果轉型便會報錯誤。成功建立泛型數組的唯一方式是建立一個類型擦除的數組,然後轉型,如程式碼: gia = (Generic<Integer>[])new Generic[SIZE],gia 的Class 物件輸出的名字是Generic[]。

我個人的理解是:由於類型擦除,所以Generic<Integer> 相當於初始類型Generic,那麼gia = (Generic<Integer>[])new Generic[SIZE] 中的轉型其實還是轉型為Generic[],看上去像沒轉,但是多了編譯器對參數的檢查和自動轉型,向數組插入new Object()new Generic< Double>()皆會報錯,而gia[0] 取出給Generic<Integer> 也不需要我們手動轉型。

使用 T[] array

上面的範例中,元素的型別是泛型類別。下面看一個元素本身型別是泛型參數的範例:

public class GenericArray<T> {
 private T[] array;
 @SuppressWarnings("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]; }
 // Method that exposes the underlying representation:
 public T[] rep() { return array; } //返回数组 会报错
 public static void main(String[] args) {
 GenericArray<Integer> gai =
 new GenericArray<Integer>(10);
 // This causes a ClassCastException:
 //! Integer[] ia = gai.rep();
 // This is OK:
 Object[] oa = gai.rep();
 }
}
登入後複製

在上面的程式碼中,泛型陣列的建立是建立一個 Object 數組,然後轉型為 T[]。但數組實際的型別還是 Object[]。在呼叫 rep()方法的時候,就報 ClassCastException 異常了,因為 Object[] 無法轉型為 Integer[]。

那建立泛型陣列的程式碼 array = (T[])new Object[sz] 為什麼不會報錯? 我的理解和前面介紹的類似,由於類型擦除,相當於轉型為Object[],看上去就是沒轉,但是多了編譯器的參數檢查和自動轉型。而如果把泛型參數改成<T extends Integer> ,那麼因為型別是擦除到第一個邊界,所以array = (T[])new Object[sz] 中相當於轉型為Integer[],這應該會報錯。以下是實驗的程式碼:

public class GenericArray<T extends Integer> {
 private T[] array;
 @SuppressWarnings("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]; }
 // Method that exposes the underlying representation:
 public T[] rep() { return array; } //返回数组 会报错
 public static void main(String[] args) {
 GenericArray<Integer> gai =
 new GenericArray<Integer>(10);
 // This causes a ClassCastException:
 //! Integer[] ia = gai.rep();
 // This is OK:
 Object[] oa = gai.rep();
 }
}
登入後複製

比起原始的版本,上面的程式碼只修改了第一行,把 <T> 改成了<T extends Integer>   那麼不用呼叫rep(),在建立泛型陣列的時候就會報錯。下面是運行結果:

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
at GenericArray.<init>(GenericArray.java:15)
登入後複製

使用 Object[] array

由于擦除,运行期的数组类型只能是 Object[],如果我们立即把它转型为 T[],那么在编译期就失去了数组的实际类型,编译器也许无法发现潜在的错误。因此,更好的办法是在内部最好使用 Object[] 数组,在取出元素的时候再转型。看下面的例子:

public class GenericArray2<T> {
 private Object[] array;
 public GenericArray2(int sz) {
 array = new Object[sz];
 }
 public void put(int index, T item) {
 array[index] = item;
 }
 @SuppressWarnings("unchecked")
 public T get(int index) { return (T)array[index]; }
 @SuppressWarnings("unchecked")
 public T[] rep() {
 return (T[])array; // Warning: unchecked cast
 }
 public static void main(String[] args) {
 GenericArray2<Integer> gai =
 new GenericArray2<Integer>(10);
 for(int i = 0; i < 10; i ++)
 gai.put(i, i);
 for(int i = 0; i < 10; i ++)
 System.out.print(gai.get(i) + " ");
 System.out.println();
 try {
 Integer[] ia = gai.rep();
 } catch(Exception e) { System.out.println(e); }
 }
} /* Output: (Sample)
0 1 2 3 4 5 6 7 8 9
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
*///:~
登入後複製

现在内部数组的呈现不是 T[] 而是 Object[],当 get() 被调用的时候数组的元素被转型为 T,这正是元素的实际类型。不过调用 rep() 还是会报错, 因为数组的实际类型依然是Object[],终究不能转换为其它类型。使用 Object[] 代替 T[] 的好处是让我们不会忘记数组运行期的实际类型,以至于不小心引入错误。

使用类型标识

其实使用 Class 对象作为类型标识是更好的设计:

public class GenericArrayWithTypeToken<T> {
 private T[] array;
 @SuppressWarnings("unchecked")
 public GenericArrayWithTypeToken(Class<T> type, int sz) {
 array = (T[])Array.newInstance(type, sz);
 }
 public void put(int index, T item) {
 array[index] = item;
 }
 public T get(int index) { return array[index]; }
 // Expose the underlying representation:
 public T[] rep() { return array; }
 public static void main(String[] args) {
 GenericArrayWithTypeToken<Integer> gai =
 new GenericArrayWithTypeToken<Integer>(
 Integer.class, 10);
 // This now works:
 Integer[] ia = gai.rep();
 }
}
登入後複製

在构造器中传入了 Class<T> 对象,通过 Array.newInstance(type, sz) 创建一个数组,这个方法会用参数中的 Class 对象作为数组元素的组件类型。这样创建出的数组的元素类型便不再是 Object,而是 T。这个方法返回 Object 对象,需要把它转型为数组。不过其他操作都不需要转型了,包括 rep() 方法,因为数组的实际类型与 T[] 是一致的。这是比较推荐的创建泛型数组的方法。

总结

数组与泛型的关系还是有点复杂的,Java 中不允许直接创建泛型数组。本文分析了其中原因并且总结了一些创建泛型数组的方式。其中有部分个人的理解,如果错误希望大家指正。下一篇会总结通配符的使用。

以上是Java泛型總結(二)-泛型與陣列的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板