Java 泛型總結(jié)(二):泛型與數(shù)組
簡(jiǎn)介
上一篇文章介紹了泛型的基本用法以及類型擦除的問(wèn)題,現(xiàn)在來(lái)看看泛型和數(shù)組的關(guān)系。數(shù)組相比于Java 類庫(kù)中的容器類是比較特殊的,主要體現(xiàn)在三個(gè)方面:
- 數(shù)組創(chuàng)建后大小便固定,但效率更高
- 數(shù)組能追蹤它內(nèi)部保存的元素的具體類型,插入的元素類型會(huì)在編譯期得到檢查
- 數(shù)組可以持有原始類型 ( int,float等 ),不過(guò)有了自動(dòng)裝箱,容器類看上去也能持有原始類型了
那么當(dāng)數(shù)組遇到泛型會(huì)怎樣? 能否創(chuàng)建泛型數(shù)組呢?這是這篇文章的主要內(nèi)容。
這個(gè)系列的另外兩篇文章:
泛型數(shù)組
如何創(chuàng)建泛型數(shù)組
如果有一個(gè)類如下:
class Generic<T> { }
如果要?jiǎng)?chuàng)建一個(gè)泛型數(shù)組,應(yīng)該是這樣: Generic<Integer> ga = new Generic<Integer>[]
不過(guò)行代碼會(huì)報(bào)錯(cuò),也就是說(shuō)不能直接創(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
是一個(gè)指向泛型數(shù)組的引用,這段代碼可以通過(guò)編譯。但是,我們并不能創(chuàng)建這個(gè)確切類型的數(shù)組,也就是不能使用 new Generic<Integer>[]
具體參見(jiàn)下面的例子:
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í)際類型,這個(gè)類型是在數(shù)組創(chuàng)建的時(shí)候建立的。上面被注釋掉的一行代碼: gia = (Generic<Integer>[])new Object[SIZE]
,數(shù)組在創(chuàng)建的時(shí)候是一個(gè) Object 數(shù)組,如果轉(zhuǎn)型便會(huì)報(bào)錯(cuò)。成功創(chuàng)建泛型數(shù)組的唯一方式是創(chuàng)建一個(gè)類型擦除的數(shù)組,然后轉(zhuǎn)型,如代碼: gia = (Generic<Integer>[])new Generic[SIZE]
,gia 的 Class 對(duì)象輸出的名字是 Generic[]。
我個(gè)人的理解是:由于類型擦除,所以 Generic<Integer> 相當(dāng)于初始類型 Generic,那么 gia = (Generic<Integer>[])new Generic[SIZE]
中的轉(zhuǎn)型其實(shí)還是轉(zhuǎn)型為 Generic[],看上去像沒(méi)轉(zhuǎn),但是多了編譯器對(duì)參數(shù)的檢查和自動(dòng)轉(zhuǎn)型,向數(shù)組插入 new Object()
和 new Generic<Double>()
均會(huì)報(bào)錯(cuò),而 gia[0] 取出給 Generic<Integer>
也不需要我們手動(dòng)轉(zhuǎn)型。
使用 T[] array
上面的例子中,元素的類型是泛型類。下面看一個(gè)元素本身類型是泛型參數(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; } //返回?cái)?shù)組 會(huì)報(bào)錯(cuò) 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)建一個(gè) Object 數(shù)組,然后轉(zhuǎn)型為 T[]。但數(shù)組實(shí)際的類型還是 Object[]。在調(diào)用 rep()方法的時(shí)候,就報(bào) ClassCastException 異常了,因?yàn)?Object[] 無(wú)法轉(zhuǎn)型為 Integer[]。
那創(chuàng)建泛型數(shù)組的代碼 array = (T[])new Object[sz]
為什么不會(huì)報(bào)錯(cuò)呢?我的理解和前面介紹的類似,由于類型擦除,相當(dāng)于轉(zhuǎn)型為 Object[]
,看上去就是沒(méi)轉(zhuǎn),但是多了編譯器的參數(shù)檢查和自動(dòng)轉(zhuǎn)型。而如果把泛型參數(shù)改成 <T extends Integer>
,那么因?yàn)轭愋褪遣脸降谝粋€(gè)邊界,所以 array = (T[])new Object[sz]
中相當(dāng)于轉(zhuǎn)型為 Integer[]
,這應(yīng)該會(huì)報(bào)錯(cuò)。下面是實(shí)驗(yàn)的代碼:
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; } //返回?cái)?shù)組 會(huì)報(bào)錯(cuò) 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ù)組的時(shí)候就會(huì)報(bào)錯(cuò)。下面是運(yùn)行結(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
由于擦除,運(yùn)行期的數(shù)組類型只能是 Object[],如果我們立即把它轉(zhuǎn)型為 T[],那么在編譯期就失去了數(shù)組的實(shí)際類型,編譯器也許無(wú)法發(fā)現(xiàn)潛在的錯(cuò)誤。因此,更好的辦法是在內(nèi)部最好使用 Object[] 數(shù)組,在取出元素的時(shí)候再轉(zhuǎn)型??聪旅娴睦樱?/p>
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í)候數(shù)組的元素被轉(zhuǎn)型為 T,這正是元素的實(shí)際類型。不過(guò)調(diào)用 rep() 還是會(huì)報(bào)錯(cuò), 因?yàn)閿?shù)組的實(shí)際類型依然是Object[],終究不能轉(zhuǎn)換為其它類型。使用 Object[] 代替 T[] 的好處是讓我們不會(huì)忘記數(shù)組運(yùn)行期的實(shí)際類型,以至于不小心引入錯(cuò)誤。
使用類型標(biāo)識(shí)
其實(shí)使用 Class 對(duì)象作為類型標(biāo)識(shí)是更好的設(shè)計(jì):
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>
對(duì)象,通過(guò) Array.newInstance(type, sz)
創(chuàng)建一個(gè)數(shù)組,這個(gè)方法會(huì)用參數(shù)中的 Class 對(duì)象作為數(shù)組元素的組件類型。這樣創(chuàng)建出的數(shù)組的元素類型便不再是 Object,而是 T。這個(gè)方法返回 Object 對(duì)象,需要把它轉(zhuǎn)型為數(shù)組。不過(guò)其他操作都不需要轉(zhuǎn)型了,包括 rep() 方法,因?yàn)閿?shù)組的實(shí)際類型與 T[] 是一致的。這是比較推薦的創(chuàng)建泛型數(shù)組的方法。
總結(jié)
數(shù)組與泛型的關(guān)系還是有點(diǎn)復(fù)雜的,Java 中不允許直接創(chuàng)建泛型數(shù)組。本文分析了其中原因并且總結(jié)了一些創(chuàng)建泛型數(shù)組的方式。其中有部分個(gè)人的理解,如果錯(cuò)誤希望大家指正。下一篇會(huì)總結(jié)通配符的使用。
以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,同時(shí)也希望多多支持腳本之家!
- Java封裝數(shù)組實(shí)現(xiàn)包含、搜索和刪除元素操作詳解
- Java封裝數(shù)組實(shí)現(xiàn)在數(shù)組中查詢?cè)睾托薷脑夭僮魇纠?/a>
- Java封裝數(shù)組之添加元素操作實(shí)例分析
- 使用java數(shù)組 封裝自己的數(shù)組操作示例
- java數(shù)組、泛型、集合在多態(tài)中的使用及對(duì)比
- java 用泛型參數(shù)類型構(gòu)造數(shù)組詳解及實(shí)例
- JAVA得到數(shù)組中最大值和最小值的簡(jiǎn)單實(shí)例
- JavaScrip數(shù)組刪除特定元素的幾種方法總結(jié)
- Java中高效的判斷數(shù)組中某個(gè)元素是否存在詳解
- java中數(shù)組的定義及使用方法(推薦)
- Java封裝數(shù)組之改進(jìn)為泛型數(shù)組操作詳解
相關(guān)文章
Java創(chuàng)建數(shù)組的幾種方式總結(jié)
下面小編就為大家?guī)?lái)一篇Java創(chuàng)建數(shù)組的幾種方式總結(jié)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10多jdk環(huán)境下指定springboot外部配置文件詳解
這篇文章主要為大家介紹了多jdk環(huán)境下指定springboot外部配置文件詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03SpringBoot @value注解動(dòng)態(tài)刷新問(wèn)題小結(jié)
@Value注解 所對(duì)應(yīng)的數(shù)據(jù)源來(lái)自項(xiàng)目的 Environment 中,我們可以將數(shù)據(jù)庫(kù)或其他文件中的數(shù)據(jù),加載到項(xiàng)目的 Environment 中,然后 @Value注解 就可以動(dòng)態(tài)獲取到配置信息了,這篇文章主要介紹了SpringBoot @value注解動(dòng)態(tài)刷新,需要的朋友可以參考下2023-09-09SpringBoot項(xiàng)目實(shí)現(xiàn)日志打印SQL的常用方法(包括SQL語(yǔ)句和參數(shù))
有時(shí)候遇到問(wèn)題需要根據(jù)我們編寫的SQL進(jìn)行分析,但如果不進(jìn)行一些開(kāi)發(fā)或者配置的話,這些SQL是不會(huì)打印到控制臺(tái)的,它們默認(rèn)是隱藏的。下面給大家介紹幾種常用的方法,感興趣的朋友跟隨小編一起看看吧2024-04-04MyBatis-Plus詳解(環(huán)境搭建、關(guān)聯(lián)操作)
MyBatis-Plus 是一個(gè) MyBatis 的增強(qiáng)工具,在 MyBatis 的基礎(chǔ)上只做增強(qiáng)不做改變,為簡(jiǎn)化開(kāi)發(fā)、提高效率而生,今天通過(guò)本文給大家介紹MyBatis-Plus環(huán)境搭建及關(guān)聯(lián)操作,需要的朋友參考下吧2022-09-09Java overload和override的區(qū)別分析
方法的重寫(Overriding)和重載(Overloading)是Java多態(tài)性的不同表現(xiàn),想要了解更多請(qǐng)參考本文2012-11-11