【奇淫巧技】Java 泛型 泛型的約束與局限性

不能用基本類型實(shí)例化類型參數(shù)

不能用類型參數(shù)代替基本類型:例如藐俺,沒有Pair<double>,只有Pair<Double>绣檬,其原因是類型擦除模狭。擦除之后渠鸽,Pair類含有Object類型的域叫乌,而Object不能存儲(chǔ)double值。這體現(xiàn)了Java語言中基本類型的獨(dú)立狀態(tài)徽缚。

運(yùn)行時(shí)類型查詢只適用于原始類型(raw type)

運(yùn)行時(shí):通常指在Classloader裝載之后憨奸,JVM執(zhí)行之時(shí)

類型查詢:instanceof、getClass凿试、強(qiáng)制類型轉(zhuǎn)換

原始類型:即(raw type),泛型類型經(jīng)編譯器類型擦除后是Object或泛型參數(shù)的限定類型(例如Pair<T extends Comparable>排宰,Comparable就是T的限定類型似芝,轉(zhuǎn)化后泛型的原始類型就是Comparable,所以Pair類不帶泛型是Pair<Comparable>)板甘,即Pair類含有Comparable類型的域

JVM中沒有泛型

if(a instanceof Pair<String>) //ERROR,僅測試了a是否是任意類型的一個(gè)Pair党瓮,會(huì)看到編譯器ERROR警告


if(a instanceof Pair<T>) //ERROR


Pair<String> p = (Pair<String>) a;//WARNING,僅測試a是否是一個(gè)Pair


Pair<String> stringPair = ...;
Pair<Employee> employeePair = ...;
if(stringPair.getClass() == employeePair.getClass()) 
 //會(huì)得到true,因?yàn)閮纱握{(diào)用getClass都將返回Pair.class
 //加入Java開發(fā)交流君樣:756584822一起吹水聊天

不能創(chuàng)建參數(shù)化類型的數(shù)組(泛型數(shù)組)

參數(shù)化類型的數(shù)組:指類型帶有泛型參數(shù)的數(shù)組盐类,也即泛型數(shù)組寞奸,如Pair<T>[] 、 T[]

不能實(shí)例化參數(shù)化類型的數(shù)組傲醉,例如:

Pair<String> table = new Pair<String>[10]; //ERROR

在這里我們假設(shè)可以實(shí)例化蝇闭,那么經(jīng)編譯器類型擦除后,table的類型是Pair[]硬毕,我們?cè)僮屗鼌f(xié)變?yōu)镺bject[]:

Object[] objArray = table;

而一般來說呻引,數(shù)組會(huì)記住他的元素類型Pair,我們?nèi)绻噲D存儲(chǔ)其他類型的元素吐咳,就會(huì)拋出異常(數(shù)組存儲(chǔ)檢查)逻悠,例如:

objArray[0] = "Hello"; //ERROR--component type is Pair

但是,對(duì)于泛型類型Pair<String>韭脊,類型擦除會(huì)使這種不同類檢查機(jī)制無效童谒,這就是不能實(shí)例化泛型數(shù)組的原因!

objArray[0] = new Pair<Employee>();  
//如果泛型機(jī)制允許我們實(shí)例化數(shù)組沪羔,那么這一步就沒理由出錯(cuò)了饥伊!
//而這違背了我們的初衷(限定類型)

數(shù)組存儲(chǔ)只會(huì)檢查擦除后的類型,又因?yàn)镴ava語言設(shè)計(jì)數(shù)組可以協(xié)變蔫饰,所以可以通過編譯
能夠通過數(shù)組存儲(chǔ)檢查琅豆,不過仍會(huì)導(dǎo)致一個(gè)類型錯(cuò)誤,故不允許創(chuàng)建參數(shù)化類型的數(shù)組
注意篓吁,聲明類型為Pair<String>[]的變量是合法的茫因,只是不能創(chuàng)建這些實(shí)例(我們應(yīng)該直接用new Pair<String>[10]{......}來初始化這個(gè)變量)

泛型數(shù)組的間接實(shí)現(xiàn):

通過泛型數(shù)組包裝器,如ArrayList類杖剪,維護(hù)一個(gè)Object數(shù)組冻押,然后通過進(jìn)出口方法set、get來限定類型和強(qiáng)制轉(zhuǎn)換數(shù)組類型盛嘿,從而間接實(shí)現(xiàn)泛型數(shù)組洛巢,

例如:ArrayList: ArrayList<Pair<T>>、ArrayList<T>

不能實(shí)例化類型變量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)造泛型對(duì)象(要注意表達(dá)式T.class是非法的)

public static <T> Pair<T> makePair(Class<T> cl){
    try{ return new Pair<>(cl.newInstance() , cl.newInstance()); }
    catch(Exception ex) { return null; }
}
//加入Java開發(fā)交流君樣:756584822一起吹水聊天
//這個(gè)方法可以按照下列方式調(diào)用:
Pair<String> p = Pair.makePair(String.class);

注意:Class類本身是泛型。String.class是一個(gè)Class<String>的實(shí)例类垦,因此makePair方法能夠推斷出pair的類型
不能使用new T[..]

解決方案:使用泛型數(shù)組包裝器狈邑,例如ArrayList
然而,當(dāng)在設(shè)計(jì)一個(gè)泛型數(shù)組包裝器時(shí)蚤认,例如方法minmax返回一個(gè)T[]數(shù)組米苹,則泛型數(shù)組包裝器無法施展,因?yàn)轭愋筒脸?code>return (T [])new Object是沒有意義的強(qiáng)轉(zhuǎn)不了砰琢。此時(shí)只好利用反射蘸嘶,調(diào)用Array.newInstance

import java.lang.reflect.*;
...
public static <T extends Comparable> T[] minmax(T... a){
    T[] mm = (T[]) Array.newInstance(a.getClass().getComponentType() , 2);
...
}

【API文檔描述】public Class<?> getComponentType() 返回表示數(shù)組組件類型的 Class。如果此類不表示數(shù)組類陪汽,則此方法返回 null训唱。
而ArrayList類中的toArray方法的實(shí)現(xiàn)就麻煩了


public Object[] toArray() 無參,返回Object[]數(shù)組即可 
public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

【API文檔描述】public static <T> T[] copyOf(T[] original,int newLength)
  復(fù)制指定的數(shù)組挚冤,截取或用 null 填充(如有必要)况增,以使副本具有指定的長度。對(duì)于在原數(shù)組和副本中都有效的所有索引训挡,這兩個(gè)數(shù)組將包含相同的值澳骤。對(duì)于在副本中有效而在原數(shù)組無效的所有索引,副本將包含 null澜薄。當(dāng)且僅當(dāng)指定長度大于原數(shù)組的長度時(shí)为肮,這些索引存在。所得數(shù)組和原數(shù)組屬于完全相同的類肤京。
public <T> T[] toArray(T[] a) a - 要存儲(chǔ)列表元素的T[]數(shù)組(如果它足夠大)否則分配一個(gè)具有相同運(yùn)行時(shí)類型的新數(shù)組颊艳,返回該T[]數(shù)組

@SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass()); //a.getClass()得運(yùn)行時(shí)目的數(shù)組的運(yùn)行時(shí)類型//加入Java開發(fā)交流君樣:756584822一起吹水聊天
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

【API文檔描述】
public static <T,U> T[] copyOf(U[] original,int newLength, Class<? extends T[]> newType)
復(fù)制指定的數(shù)組,截取或用 null 填充(如有必要)忘分,以使副本具有指定的長度棋枕。對(duì)于在原數(shù)組和副本中都有效的所有索引,這兩個(gè)數(shù)組將包含相同的值饭庞。對(duì)于在副本中有效而在原數(shù)組無效的所有索引戒悠,副本將包含 null。當(dāng)且僅當(dāng)指定長度大于原數(shù)組的長度時(shí)舟山,這些索引存在绸狐。所得數(shù)組屬于 newType 類。
泛型類的靜態(tài)上下文中類型變量無效

泛型類不能在靜態(tài)域或靜態(tài)方法中引用類型變量

public class Singleton<T>{
    private static T singleInstance; //ERROR
    public static T getSingleInstance(){...} //ERROR
}

類型擦除后只剩下Singleton類累盗,因?yàn)殪o態(tài)所以他只包含一個(gè)singleInstance域寒矿,如果能運(yùn)行則以Singleton類為模板生成不同類型的域,因此產(chǎn)生了沖突

不能throws或catch泛型類的實(shí)例(有關(guān)異常)

泛型類繼承Throwable類不合法若债,如public class Problem<T> extends Exception {...}//ERROR 不能通過編譯
catch子句不能使用類型變量

public static <T extends Throwable> void doWork(Class<T> t){
    try{
            do work
        }catch (T e){ // ERROR
            Logger.global.info(...)
        }
}

不過符相,在異常規(guī)范中使用類型變量是允許的:

public static <T extends Throwable> void doWork(T t) throws T { //此時(shí)可以throws T
    try{//加入Java開發(fā)交流君樣:756584822一起吹水聊天
            do work
        }catch (Throwable realCause){ //捕獲到具體實(shí)例
            t.initCause(realCause); 
            throw t; //這時(shí)候拋具體實(shí)例,所以throw t 和 throws T 是可以的!
        }
}

此特性作用:可以利用泛型類、類型擦除啊终、SuppressWarnings標(biāo)注镜豹,來消除對(duì)已檢查(checked)異常的檢查,
unchecked和checked異常: Java語言規(guī)范將派生于Error類或RuntimeException的所有異常稱為未檢查(unchecked)異常蓝牲,其他的是已檢查(checked)異常

  • Java異常處理原則:必須為所有已檢查(checked)異常提供一個(gè)處理器趟脂,即一對(duì)一個(gè),多對(duì)多個(gè)
 @SuppressWarnings("unchecked") 
   //SuppressWarning標(biāo)注很關(guān)鍵例衍,使得編譯器認(rèn)為T是unchecked異常從而不強(qiáng)迫為每一個(gè)異常提供處理器
public static <T extends Throwable> void throwAs(Throwable e) throws
   T{  //因?yàn)榉盒秃皖愋筒脸羝冢梢詡鬟f任意checked異常,例如RuntimeException類異常

    throw (T) e;
}

假設(shè)該方法放在類Block中佛玄,如果調(diào)用 Block.<RuntimeException>throwAs(t); 編譯器就會(huì)認(rèn)為t是一個(gè)未檢查的異常

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 ;
    }
}

再寫個(gè)測試類

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();
    }
}
  • 啟動(dòng)線程后梦抢,throwAs方法將捕獲線程run方法所有checked異常般贼,“處理”成unchecked
    Exception(其實(shí)只是騙了編譯器)后拋出;

有什么意義惑申?正常情況下具伍,因?yàn)閞un()方法聲明為不拋出任何checked異常,所以必須捕獲所有checked異常并“包裝”到未檢查的異常中圈驼;意義:而我們這樣處理后人芽,就不必去捕獲所有并包裝到unchecked異常中,我們只是拋出異常并“哄騙”了編譯器而已
注意擦除后的沖突

Java泛型規(guī)范有個(gè)原則:“要想支持擦除的轉(zhuǎn)換绩脆,就需要強(qiáng)行限制一個(gè)泛型類或類型變量T不能同時(shí)成為兩個(gè)接口類型的子類萤厅,而這兩個(gè)接口是統(tǒng)一接口的不同參數(shù)化”
注意:非泛型類可以同時(shí)實(shí)現(xiàn)同一接口,畢竟沒有泛型靴迫,很好處理

class Calender implements Comparable<Calender>{...}

class GGCalender extends Calender implements Comparable<GGCalender>{...} //ERROR

在這里GGCalender類會(huì)同時(shí)實(shí)現(xiàn)Comparable<Calender> 和 Comparable<GGCalender>惕味,這是同一接口的不同參數(shù)化

image

最新2020整理收集的一些高頻面試題(都整理成文檔),有很多干貨玉锌,包含mysql名挥,netty,spring主守,線程禀倔,spring cloud、jvm参淫、源碼救湖、算法等詳細(xì)講解,也有詳細(xì)的學(xué)習(xí)規(guī)劃圖涎才,面試題整理等鞋既,需要獲取這些內(nèi)容的朋友請(qǐng)加Q君樣:756584822

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子邑闺,更是在濱河造成了極大的恐慌跌前,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件检吆,死亡現(xiàn)場離奇詭異舒萎,居然都是意外死亡程储,警方通過查閱死者的電腦和手機(jī)蹭沛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來章鲤,“玉大人摊灭,你說我怎么就攤上這事“芑玻” “怎么了帚呼?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長皱蹦。 經(jīng)常有香客問我煤杀,道長,這世上最難降的妖魔是什么沪哺? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任沈自,我火速辦了婚禮,結(jié)果婚禮上辜妓,老公的妹妹穿的比我還像新娘枯途。我一直安慰自己,他們只是感情好籍滴,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布酪夷。 她就那樣靜靜地躺著,像睡著了一般孽惰。 火紅的嫁衣襯著肌膚如雪晚岭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天勋功,我揣著相機(jī)與錄音坦报,去河邊找鬼。 笑死酝润,一個(gè)胖子當(dāng)著我的面吹牛燎竖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播要销,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼构回,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起纤掸,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤脐供,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后借跪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體政己,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年掏愁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了歇由。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡果港,死狀恐怖沦泌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情辛掠,我是刑警寧澤谢谦,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站萝衩,受9級(jí)特大地震影響回挽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜猩谊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一千劈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧预柒,春花似錦队塘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至淋袖,卻和暖如春鸿市,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背即碗。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來泰國打工焰情, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人剥懒。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓内舟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親初橘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子验游,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容