Java 泛型 泛型的约束与局限性
不能用基本類型實例化類型參數(shù)
不能用類型參數(shù)代替基本類型:例如,沒有Pair,只有Pair,其原因是類型擦除。擦除之后,Pair類含有Object類型的域,而Object不能存儲double值。這體現(xiàn)了Java語言中基本類型的獨立狀態(tài)。
運行時類型查詢只適用于原始類型(raw type)
運行時:通常指在Classloader裝載之后,JVM執(zhí)行之時
類型查詢:instanceof、getClass、強(qiáng)制類型轉(zhuǎn)換
原始類型:即(raw type),泛型類型經(jīng)編譯器類型擦除后是Object或泛型參數(shù)的限定類型(例如Pair,Comparable就是T的限定類型,轉(zhuǎn)化后泛型的原始類型就是Comparable,所以Pair類不帶泛型是Pair),即Pair類含有Comparable類型的域
JVM中沒有泛型
if(a instanceof Pair<String>) //ERROR,僅測試了a是否是任意類型的一個Pair,會看到編譯器ERROR警告if(a instanceof Pair<T>) //ERRORPair<String> p = (Pair<String>) a;//WARNING,僅測試a是否是一個PairPair<String> stringPair = ...; Pair<Employee> employeePair = ...; if(stringPair.getClass() == employeePair.getClass()) //會得到true,因為兩次調(diào)用getClass都將返回Pair.class//加入Java開發(fā)交流君樣:756584822一起吹水聊天不能創(chuàng)建參數(shù)化類型的數(shù)組(泛型數(shù)組)
參數(shù)化類型的數(shù)組:指類型帶有泛型參數(shù)的數(shù)組,也即泛型數(shù)組,如Pair[] 、 T[]
不能實例化參數(shù)化類型的數(shù)組,例如:
Pair<String> table = new Pair<String>[10]; //ERROR在這里我們假設(shè)可以實例化,那么經(jīng)編譯器類型擦除后,table的類型是Pair[],我們再讓它協(xié)變?yōu)镺bject[]:
Object[] objArray = table;而一般來說,數(shù)組會記住他的元素類型Pair,我們?nèi)绻噲D存儲其他類型的元素,就會拋出異常(數(shù)組存儲檢查),例如:
objArray[0] = "Hello"; //ERROR--component type is Pair但是,對于泛型類型Pair,類型擦除會使這種不同類檢查機(jī)制無效,這就是不能實例化泛型數(shù)組的原因!
objArray[0] = new Pair<Employee>(); //如果泛型機(jī)制允許我們實例化數(shù)組,那么這一步就沒理由出錯了! //而這違背了我們的初衷(限定類型)數(shù)組存儲只會檢查擦除后的類型,又因為Java語言設(shè)計數(shù)組可以協(xié)變,所以可以通過編譯
能夠通過數(shù)組存儲檢查,不過仍會導(dǎo)致一個類型錯誤,故不允許創(chuàng)建參數(shù)化類型的數(shù)組
注意,聲明類型為Pair[]的變量是合法的,只是不能創(chuàng)建這些實例(我們應(yīng)該直接用new Pair[10]{…}來初始化這個變量)
泛型數(shù)組的間接實現(xiàn):
通過泛型數(shù)組包裝器,如ArrayList類,維護(hù)一個Object數(shù)組,然后通過進(jìn)出口方法set、get來限定類型和強(qiáng)制轉(zhuǎn)換數(shù)組類型,從而間接實現(xiàn)泛型數(shù)組,
例如:ArrayList: ArrayList<Pair<T>>、ArrayList<T>不能實例化類型變量T
即不能使用new T(..) , new T[..] 或 T.class這樣的表達(dá)式中的類型變量
例如: public Pair() { first = new T(); } //ERROR!類型擦除將T改變成Object,調(diào)用非本意的new Object()
不能使用new T(..)
但是,可通過反射調(diào)用Class.newInstance方法來構(gòu)造泛型對象(要注意表達(dá)式T.class是非法的)
注意:Class類本身是泛型。String.class是一個Class的實例,因此makePair方法能夠推斷出pair的類型
不能使用new T[…]
解決方案:使用泛型數(shù)組包裝器,例如ArrayList
然而,當(dāng)在設(shè)計一個泛型數(shù)組包裝器時,例如方法minmax返回一個T[]數(shù)組,則泛型數(shù)組包裝器無法施展,因為類型擦除,return (T [])new Object是沒有意義的強(qiáng)轉(zhuǎn)不了。此時只好利用反射,調(diào)用Array.newInstance:
【API文檔描述】public Class<?> getComponentType() 返回表示數(shù)組組件類型的 Class。如果此類不表示數(shù)組類,則此方法返回 null。
而ArrayList類中的toArray方法的實現(xiàn)就麻煩了
【API文檔描述】public static T[] copyOf(T[] original,int newLength)
復(fù)制指定的數(shù)組,截取或用 null 填充(如有必要),以使副本具有指定的長度。對于在原數(shù)組和副本中都有效的所有索引,這兩個數(shù)組將包含相同的值。對于在副本中有效而在原數(shù)組無效的所有索引,副本將包含 null。當(dāng)且僅當(dāng)指定長度大于原數(shù)組的長度時,這些索引存在。所得數(shù)組和原數(shù)組屬于完全相同的類。
public T[] toArray(T[] a) a - 要存儲列表元素的T[]數(shù)組(如果它足夠大)否則分配一個具有相同運行時類型的新數(shù)組,返回該T[]數(shù)組
【API文檔描述】
public static <T,U> T[] copyOf(U[] original,int newLength, Class<? extends T[]> newType)
復(fù)制指定的數(shù)組,截取或用 null 填充(如有必要),以使副本具有指定的長度。對于在原數(shù)組和副本中都有效的所有索引,這兩個數(shù)組將包含相同的值。對于在副本中有效而在原數(shù)組無效的所有索引,副本將包含 null。當(dāng)且僅當(dāng)指定長度大于原數(shù)組的長度時,這些索引存在。所得數(shù)組屬于 newType 類。
泛型類的靜態(tài)上下文中類型變量無效
泛型類不能在靜態(tài)域或靜態(tài)方法中引用類型變量
public class Singleton<T>{private static T singleInstance; //ERRORpublic static T getSingleInstance(){...} //ERROR }類型擦除后只剩下Singleton類,因為靜態(tài)所以他只包含一個singleInstance域,如果能運行則以Singleton類為模板生成不同類型的域,因此產(chǎn)生了沖突
不能throws或catch泛型類的實例(有關(guān)異常)
泛型類繼承Throwable類不合法,如public class Problem<T> extends Exception {...}//ERROR 不能通過編譯
catch子句不能使用類型變量
不過,在異常規(guī)范中使用類型變量是允許的:
public static <T extends Throwable> void doWork(T t) throws T { //此時可以throws Ttry{//加入Java開發(fā)交流君樣:756584822一起吹水聊天do work}catch (Throwable realCause){ //捕獲到具體實例t.initCause(realCause); throw t; //這時候拋具體實例,所以throw t 和 throws T 是可以的!} }此特性作用:可以利用泛型類、類型擦除、SuppressWarnings標(biāo)注,來消除對已檢查(checked)異常的檢查,
unchecked和checked異常: Java語言規(guī)范將派生于Error類或RuntimeException的所有異常稱為未檢查(unchecked)異常,其他的是已檢查(checked)異常
- Java異常處理原則:必須為所有已檢查(checked)異常提供一個處理器,即一對一個,多對多個
假設(shè)該方法放在類Block中,如果調(diào)用 Block.throwAs(t); 編譯器就會認(rèn)為t是一個未檢查的異常
public abstract class Block{public abstract void body() throws Exception;public Thread toThread(){return new Thread(){public void run(){try{body();}catch(Throwable t){Block.<RuntimeException>throwAs(t);}//加入Java開發(fā)交流君樣:756584822一起吹水聊天}};}@SuppressWarnings("unchecked")public static <T extends Throwable> void throwAs(Throwable e) throws T{throw (T) e ;} }再寫個測試類
public class Test{public static void main(String[] args){new Block(){public void body() throws Exception{//不存在ixenos文件將產(chǎn)生IOException,checked異常!Scanner in = new Scanner(new File("ixenos"));while(in.hasNext())System.out.println(in.next());}//加入Java開發(fā)交流君樣:756584822一起吹水聊天}.toThread().start();} }- 啟動線程后,throwAs方法將捕獲線程run方法所有checked異常,“處理”成unchecked
Exception(其實只是騙了編譯器)后拋出;
有什么意義?正常情況下,因為run()方法聲明為不拋出任何checked異常,所以必須捕獲所有checked異常并“包裝”到未檢查的異常中;意義:而我們這樣處理后,就不必去捕獲所有并包裝到unchecked異常中,我們只是拋出異常并“哄騙”了編譯器而已
注意擦除后的沖突
Java泛型規(guī)范有個原則:“要想支持擦除的轉(zhuǎn)換,就需要強(qiáng)行限制一個泛型類或類型變量T不能同時成為兩個接口類型的子類,而這兩個接口是統(tǒng)一接口的不同參數(shù)化”
注意:非泛型類可以同時實現(xiàn)同一接口,畢竟沒有泛型,很好處理
在這里GGCalender類會同時實現(xiàn)Comparable 和 Comparable,這是同一接口的不同參數(shù)化
總結(jié)
以上是生活随笔為你收集整理的Java 泛型 泛型的约束与局限性的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java开发之上班摸鱼!写最少的代码!
- 下一篇: 《喋血复仇》单机补丁发布