Java 泛型總結(jié)(二):泛型與數(shù)組
簡介
上一篇文章介紹了泛型的基本用法以及類型擦除的問題,現(xiàn)在來看看泛型和數(shù)組的關(guān)系。數(shù)組相比于Java 類庫中的容器類是比較特殊的,主要體現(xiàn)在三個方面:
- 數(shù)組創(chuàng)建后大小便固定,但效率更高
- 數(shù)組能追蹤它內(nèi)部保存的元素的具體類型,插入的元素類型會在編譯期得到檢查
- 數(shù)組可以持有原始類型 ( int,float等 ),不過有了自動裝箱,容器類看上去也能持有原始類型了
那么當(dāng)數(shù)組遇到泛型會怎樣? 能否創(chuàng)建泛型數(shù)組呢?這是這篇文章的主要內(nèi)容。
這個系列的另外兩篇文章:
泛型數(shù)組
如何創(chuàng)建泛型數(shù)組
如果有一個類如下:
class Generic<T> {
}
如果要創(chuàng)建一個泛型數(shù)組,應(yīng)該是這樣: Generic<Integer> ga = new Generic<Integer>[] 不過行代碼會報錯,也就是說不能直接創(chuàng)建泛型數(shù)組。
那么如果要使用泛型數(shù)組怎么辦?一種方案是使用 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); }
}
如何創(chuàng)建真正的泛型數(shù)組呢?我們不能直接創(chuàng)建,但可以定義泛型數(shù)組的引用。比如:
public class ArrayOfGenericReference {
static Generic<Integer>[] gia;
}
gia 是一個指向泛型數(shù)組的引用,這段代碼可以通過編譯。但是,我們并不能創(chuàng)建這個確切類型的數(shù)組,也就是不能使用 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[]
*///:~
數(shù)組能追蹤元素的實際類型,這個類型是在數(shù)組創(chuàng)建的時候建立的。上面被注釋掉的一行代碼: gia = (Generic<Integer>[])new Object[SIZE],數(shù)組在創(chuàng)建的時候是一個 Object 數(shù)組,如果轉(zhuǎn)型便會報錯。成功創(chuàng)建泛型數(shù)組的唯一方式是創(chuàng)建一個類型擦除的數(shù)組,然后轉(zhuǎn)型,如代碼: gia = (Generic<Integer>[])new Generic[SIZE],gia 的 Class 對象輸出的名字是 Generic[]。
我個人的理解是:由于類型擦除,所以 Generic<Integer> 相當(dāng)于初始類型 Generic,那么 gia = (Generic<Integer>[])new Generic[SIZE] 中的轉(zhuǎn)型其實還是轉(zhuǎn)型為 Generic[],看上去像沒轉(zhuǎn),但是多了編譯器對參數(shù)的檢查和自動轉(zhuǎn)型,向數(shù)組插入 new Object()和 new Generic<Double>()均會報錯,而 gia[0] 取出給 Generic<Integer> 也不需要我們手動轉(zhuǎn)型。
使用 T[] array
上面的例子中,元素的類型是泛型類。下面看一個元素本身類型是泛型參數(shù)的例子:
public class GenericArray<T> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArray(int sz) {
array = (T[])new Object[sz]; // 創(chuàng)建泛型數(shù)組
}
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; } //返回數(shù)組 會報錯
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();
}
}
在上面的代碼中,泛型數(shù)組的創(chuàng)建是創(chuàng)建一個 Object 數(shù)組,然后轉(zhuǎn)型為 T[]。但數(shù)組實際的類型還是 Object[]。在調(diào)用 rep()方法的時候,就報 ClassCastException 異常了,因為 Object[] 無法轉(zhuǎn)型為 Integer[]。
那創(chuàng)建泛型數(shù)組的代碼 array = (T[])new Object[sz] 為什么不會報錯呢?我的理解和前面介紹的類似,由于類型擦除,相當(dāng)于轉(zhuǎn)型為 Object[],看上去就是沒轉(zhuǎn),但是多了編譯器的參數(shù)檢查和自動轉(zhuǎn)型。而如果把泛型參數(shù)改成 <T extends Integer> ,那么因為類型是擦除到第一個邊界,所以 array = (T[])new Object[sz] 中相當(dāng)于轉(zhuǎn)型為 Integer[],這應(yīng)該會報錯。下面是實驗的代碼:
public class GenericArray<T extends Integer> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArray(int sz) {
array = (T[])new Object[sz]; // 創(chuàng)建泛型數(shù)組
}
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; } //返回數(shù)組 會報錯
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> 那么不用調(diào)用 rep(),在創(chuàng)建泛型數(shù)組的時候就會報錯。下面是運行結(jié)果:
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
由于擦除,運行期的數(shù)組類型只能是 Object[],如果我們立即把它轉(zhuǎn)型為 T[],那么在編譯期就失去了數(shù)組的實際類型,編譯器也許無法發(fā)現(xiàn)潛在的錯誤。因此,更好的辦法是在內(nèi)部最好使用 Object[] 數(shù)組,在取出元素的時候再轉(zhuǎn)型。看下面的例子:
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;
*///:~
現(xiàn)在內(nèi)部數(shù)組的呈現(xiàn)不是 T[] 而是 Object[],當(dāng) get() 被調(diào)用的時候數(shù)組的元素被轉(zhuǎn)型為 T,這正是元素的實際類型。不過調(diào)用 rep() 還是會報錯, 因為數(shù)組的實際類型依然是Object[],終究不能轉(zhuǎn)換為其它類型。使用 Object[] 代替 T[] 的好處是讓我們不會忘記數(shù)組運行期的實際類型,以至于不小心引入錯誤。
使用類型標(biāo)識
其實使用 Class 對象作為類型標(biāo)識是更好的設(shè)計:
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();
}
}
在構(gòu)造器中傳入了 Class<T> 對象,通過 Array.newInstance(type, sz) 創(chuàng)建一個數(shù)組,這個方法會用參數(shù)中的 Class 對象作為數(shù)組元素的組件類型。這樣創(chuàng)建出的數(shù)組的元素類型便不再是 Object,而是 T。這個方法返回 Object 對象,需要把它轉(zhuǎn)型為數(shù)組。不過其他操作都不需要轉(zhuǎn)型了,包括 rep() 方法,因為數(shù)組的實際類型與 T[] 是一致的。這是比較推薦的創(chuàng)建泛型數(shù)組的方法。
總結(jié)
數(shù)組與泛型的關(guān)系還是有點復(fù)雜的,Java 中不允許直接創(chuàng)建泛型數(shù)組。本文分析了其中原因并且總結(jié)了一些創(chuàng)建泛型數(shù)組的方式。其中有部分個人的理解,如果錯誤希望大家指正。下一篇會總結(jié)通配符的使用。
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時也希望多多支持腳本之家!
- Java封裝數(shù)組實現(xiàn)包含、搜索和刪除元素操作詳解
- Java封裝數(shù)組實現(xiàn)在數(shù)組中查詢元素和修改元素操作示例
- Java封裝數(shù)組之添加元素操作實例分析
- 使用java數(shù)組 封裝自己的數(shù)組操作示例
- java數(shù)組、泛型、集合在多態(tài)中的使用及對比
- java 用泛型參數(shù)類型構(gòu)造數(shù)組詳解及實例
- JAVA得到數(shù)組中最大值和最小值的簡單實例
- JavaScrip數(shù)組刪除特定元素的幾種方法總結(jié)
- Java中高效的判斷數(shù)組中某個元素是否存在詳解
- java中數(shù)組的定義及使用方法(推薦)
- Java封裝數(shù)組之改進(jìn)為泛型數(shù)組操作詳解
相關(guān)文章
Java的Hibernate框架中復(fù)合主鍵映射的創(chuàng)建和使用教程
復(fù)合主鍵映射用起來比普通的增加主鍵字段要復(fù)雜,這里我們就來共同學(xué)習(xí)Java的Hibernate框架中復(fù)合主鍵映射的創(chuàng)建和使用教程,需要的朋友可以參考下2016-07-07
出現(xiàn)SLF4J:?Failed?to?load?class?“org.slf4j.impl.StaticLog
本文主要介紹了出現(xiàn)SLF4J:?Failed?to?load?class?“org.slf4j.impl.StaticLoggerBinder“.的解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
EasyUi+Spring Data 實現(xiàn)按條件分頁查詢的實例代碼
這篇文章主要介紹了EasyUi+Spring Data 實現(xiàn)按條件分頁查詢的實例代碼,非常具有實用價值,需要的朋友可以參考下2017-07-07
SpringCloud中NacosNamingService的作用詳解
這篇文章主要介紹了SpringCloud中NacosNamingService的作用詳解,NacosNamingService類完成服務(wù)實例注冊,撤銷與獲取服務(wù)實例操作,NacosNamingService初始化采用單例模式,使用反射生成,需要的朋友可以參考下2023-11-11

