泛型通配符

通配符

首先,要展示數(shù)組的一種特殊行為瀑粥,可以向?qū)С鲱?lèi)型的數(shù)組賦予基類(lèi)型的數(shù)組引用恒序。

class Fruit {
}

class Apple extends Fruit {
}

class Jonathan extends Apple {
}

class Orange extends Fruit {
}

public class CovariantArrays {
    public static void main(String[] args) {
        Fruit[] fruits = new Apple[10];
        fruits[0] = new Apple();
        fruits[1] = new Jonathan();
        try {
            fruits[0] = new Fruit();
        } catch (Exception e) {
            System.out.println(e);
        }
        try {
            fruits[0] = new Orange();
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}
// Outputs
java.lang.ArrayStoreException: com.daidaijie.generices.holder.Fruit
java.lang.ArrayStoreException: com.daidaijie.generices.holder.Orange

main()的第一行創(chuàng)建了一個(gè)Apple數(shù)組衷咽,并將其賦值給一個(gè)Fruit數(shù)組引用。這是有意義的蒜绽,因?yàn)?code>Apple也是一種Fruit兵罢,所以Apple數(shù)組應(yīng)該也是一個(gè)Fruit數(shù)組。
但是,如果實(shí)際的數(shù)組類(lèi)型是Apple[]滓窍,你應(yīng)該只能在其中放置Apple或者Apple的子類(lèi)型,這在編譯期和運(yùn)行時(shí)都可以工作巩那。但是要注意的是吏夯,編譯器允許Fruit放置到這個(gè)數(shù)組中,這對(duì)于編譯器來(lái)說(shuō)是有意義的即横,因?yàn)樗幸粋€(gè)Fruit引用——它有什么理由不允許將這個(gè)Fruit對(duì)象或者任意從Fruit繼承出來(lái)的對(duì)象(例如Orange)放置到這個(gè)數(shù)組中呢噪生?因此在編譯期,這是允許的东囚。但是跺嗽,運(yùn)行時(shí)數(shù)組機(jī)制知道它是Apple,因此會(huì)在向數(shù)組中放置異構(gòu)類(lèi)型時(shí)拋出異常页藻。
實(shí)際上桨嫁,向上轉(zhuǎn)型并不合適用在這里。這里真正做的是將一個(gè)數(shù)組賦值為另一個(gè)數(shù)組份帐。數(shù)組的行為應(yīng)該是它可以持有其他對(duì)象璃吧,這里只是因?yàn)槲覀兡軌蛳蛏限D(zhuǎn)型而已,所以很明顯废境,數(shù)組對(duì)象可以保留有關(guān)它們包含的對(duì)象類(lèi)型的規(guī)則畜挨。就好像數(shù)組對(duì)它們持有的對(duì)象是有意識(shí)的,因?yàn)樵诰幾g期檢查和運(yùn)行時(shí)檢查之間噩凹,你不能濫用它們巴元。
對(duì)數(shù)組的這種賦值并不是那么可怕,因?yàn)樵谶\(yùn)行時(shí)可以發(fā)現(xiàn)你已經(jīng)插入不正確的類(lèi)型驮宴,但是泛型的主要目的是將這種錯(cuò)誤檢測(cè)能夠移入到編譯期逮刨。因此當(dāng)試圖使用泛型容器來(lái)代替數(shù)組的時(shí)候,會(huì)發(fā)生什么呢幻赚。

public class NonCovariantGenerics {
    // compile error
    List<Fruit> flist = new ArrayList<Apple>;
}

第一次看這段代碼的時(shí)候會(huì)認(rèn)為,"不能講一個(gè)Apple容器賦值給一個(gè)Fruit容器"禀忆。但是,泛型不僅和容器相關(guān)正確的說(shuō)法是落恼,"不能把一個(gè)涉及Apple的泛型賦值給一個(gè)涉及Fruit的泛型"箩退。如果就像在數(shù)組的情況一樣,編譯器對(duì)代碼的了解足夠多佳谦,就可以確定所涉及到的容器戴涝,,那么它可能會(huì)留下一些余地。但是它不知道任何有關(guān)這方面的信息啥刻,因此她拒絕向上轉(zhuǎn)型奸鸯。然而這根本不是向上轉(zhuǎn)型——AppleList不是FruitListAppleList將持有Apple的子類(lèi)型可帽,而Fruit將持有任何類(lèi)型的Fruit,誠(chéng)然娄涩,這包括Apple在內(nèi),但是它不是一個(gè)AppleList映跟,它仍舊是FruitList蓄拣。AppleList在類(lèi)型上不等價(jià)于FruitList,即使Apple是一種Fruit類(lèi)型努隙。
而真正的問(wèn)題是在談?wù)撊萜鞯念?lèi)型球恤,而不是容器持有的類(lèi)型。與數(shù)組不同荸镊,泛型沒(méi)有內(nèi)建的協(xié)變類(lèi)型咽斧。這是因?yàn)閿?shù)組在語(yǔ)言中是完全定義的,因此內(nèi)建了編譯期和運(yùn)行期的檢查躬存,但是在使用泛型時(shí)张惹,編譯器和運(yùn)行時(shí)系統(tǒng)都不知道你想用類(lèi)型干什么,以及應(yīng)該采用什么樣的規(guī)則优构。
但是有時(shí)候诵叁,想在兩個(gè)類(lèi)型之間建立某種向上轉(zhuǎn)型的關(guān)系,這正是通配符允許的钦椭。

public class GenericsAndCovariance {
    public static void main(String[] args) {
        List<? extends Fruit> flist = new ArrayList<>();
        // flist.add(new Apple());
        // flist.add(new Fruit());
        // flist.add(new Object());
        flist.add(null); //合法但是沒(méi)有意義
        // 我們至少知道這會(huì)返回Fruit類(lèi)型
        Fruit f = flist.get(0);
    }
}

flist類(lèi)型現(xiàn)在是List<? extends Fruit>拧额,可以將其讀作"具有任何從Fruit繼承的類(lèi)型的類(lèi)型的列表",但是這實(shí)際上并不意味著這個(gè)List將持有任何類(lèi)型的Fruit彪腔。通配符引用的是明確的類(lèi)型侥锦,一次它意味著"某種flist引用沒(méi)有指定具體的類(lèi)型"。因此這個(gè)被賦值的List必須持有諸如Fruit或者Apple這樣的某種指定的類(lèi)型德挣,但是為了向上轉(zhuǎn)型為flist恭垦,這個(gè)類(lèi)型是什么沒(méi)人關(guān)心。
如果唯一的限制是這個(gè)List要持有某種具體的Fruit或者Fruit的子類(lèi)型格嗅,但是實(shí)際上并不關(guān)心它是什么番挺,那么可以用這樣的List做什么呢?如果不知道List要持有什么類(lèi)型屯掖,那么怎么樣才能向其中安全地添加對(duì)象呢玄柏,就像在CovariantArrays.java中向上轉(zhuǎn)型數(shù)組一樣,所以答案是不能贴铜,除非編譯器而不是運(yùn)行時(shí)系統(tǒng)可以阻止這種操作的發(fā)生粪摘,但是很快會(huì)發(fā)現(xiàn)這一問(wèn)題瀑晒。
現(xiàn)在事情有點(diǎn)極端了,因?yàn)椴荒芟騽倓偮暶鬟^(guò)將持有Apple對(duì)象的List放置一個(gè)Apple對(duì)象了徘意。但是編譯器并不知道這一點(diǎn)苔悦。List<?extends Fruit>可以合法地指向一個(gè)List<Orange>椎咧。因此一旦執(zhí)行了這種類(lèi)型的向上轉(zhuǎn)型玖详,就會(huì)丟失掉向其中傳遞任何對(duì)象的能力,就算是傳遞Object也不行勤讽。
但另一方面竹宋,如果調(diào)用的是一個(gè)返回Fruit的方法,則是安全的地技,因?yàn)檫@個(gè)List中任何的對(duì)象至少具有Fruit類(lèi)型,因此編譯器允許這樣做秒拔。

“智能”的編譯器

編譯器不一定會(huì)阻止通配符修飾的泛型類(lèi)中莫矗,調(diào)用任何接受參數(shù)的方法。

public class CompilerIntelligence {

    public static void main(String[] args) {
        List<? extends Fruit> flist = Arrays.asList(new Apple());
        Apple a = (Apple) flist.get(0); // no warning
        flist.contains(new Apple()); // args is 'Object'
        flist.indexOf(new Apple()); // args is 'Object'
    }
}

上面代碼中砂缩,對(duì)contains()indexOf()的調(diào)用作谚,這兩個(gè)方法都接受Apple對(duì)象作為參數(shù),而這些調(diào)用都可以正常執(zhí)行庵芭。這不是因?yàn)榫幾g器會(huì)去檢查代碼妹懒,以查看特定的方法是否修改了某個(gè)對(duì)象,而是因?yàn)?code>contains()和indexOf()將接受Object類(lèi)型的參數(shù)双吆。而add()卻是接收了一個(gè)具有泛型參數(shù)類(lèi)型的參數(shù)眨唬,因?yàn)楫?dāng)指定一個(gè)ArrayList<? extends Fruits>時(shí),add()的參數(shù)就變成了好乐?Extends Fruits匾竿,從這個(gè)描述中編譯器并不能了解到這里需要Fruits的哪個(gè)具體子類(lèi)型,因此它也不會(huì)接收任何類(lèi)型的Fruit蔚万。即使是將Apple向上轉(zhuǎn)型為Fruit岭妖,也無(wú)關(guān)緊要——編譯器將直接拒絕對(duì)參數(shù)列表設(shè)計(jì)通配符的方法(例如add()的調(diào)用)。
在調(diào)用contains()indexOf()時(shí)反璃,參數(shù)類(lèi)型是Object昵慌,因此不涉及任何通配符,而編譯器也將允許這個(gè)調(diào)用淮蜈。
所以這意味著斋攀,這將由泛型類(lèi)的設(shè)計(jì)者來(lái)決定哪種調(diào)用是“安全的”,并使用Object作為其參數(shù)類(lèi)型礁芦。而為了在類(lèi)型中使用通配符的情況下禁止這類(lèi)調(diào)用蜻韭,我們需要在參數(shù)中使用類(lèi)型參數(shù)悼尾。
可以在一個(gè)簡(jiǎn)單的Holder類(lèi)中看到這一點(diǎn)。

public class Holder<T> {
    private T value;

    public Holder() {
    }

    public Holder(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }

    public void set(T value) {
        this.value = value;
    }

    @Override
    public boolean equals(Object obj) {
        return value.equals(obj);
    }

    public static void main(String[] args) {
        Holder<Apple> apple = new Holder<>(new Apple());
        Apple d = apple.get();

        // can't not upcast
        // Holder<Fruit> fruit = apple;
        Holder<? extends Fruit> fruit = apple; // Ok
        Fruit p = fruit.get();
        try {
            Orange c = (Orange) fruit.get();
        } catch (Exception e) {
            System.out.println(e);
            // fruit.set(new Apple()); Can't call set()
            // fruit.set(new Fruit); Can't call set()
            System.out.println(fruit.equals(d)); // Ok
        }
    }
}
// Outputs
java.lang.ClassCastException: com.daidaijie.generices.holder.Apple cannot be cast to com.daidaijie.generices.holder.Orange
true

Holder有一個(gè)接受T類(lèi)型對(duì)象的set()方法肖方,和一個(gè)get()方法闺魏,以及一個(gè)接受Object對(duì)象的equals()方法。
可以看到代碼中俯画,Holder<Apple>不能向上轉(zhuǎn)型為Holder<Fruits>析桥,但是可以向上轉(zhuǎn)型為Holder<? extends Fruits>艰垂。如果調(diào)用get()泡仗,它只會(huì)返回一個(gè)Fruit——這就是在給定“任何擴(kuò)展自Fruit的對(duì)象”這一邊界之后,它所能知道的一切猜憎。如果能夠了解更多的信息娩怎,那么可以轉(zhuǎn)型到某種具體的Fruit類(lèi)型,而這不會(huì)調(diào)至任何的警告胰柑,但是存在得到ClassCastException的風(fēng)險(xiǎn)截亦。set()方法不能工作于AppleFruit,因?yàn)?code>set的參數(shù)也是“? extends Fruit”柬讨,這意味它可以是任何事物崩瓤,而編譯器無(wú)法驗(yàn)證“任何事物”的類(lèi)型安全性。
但是踩官,equals()方法工作良好却桶,因?yàn)樗鼘⒔邮?code>Object類(lèi)型而并非T類(lèi)型的參數(shù)。因此蔗牡,編譯器只關(guān)注傳遞進(jìn)來(lái)和要返回的對(duì)象類(lèi)型颖系,它并不會(huì)分析代碼,以查看是否執(zhí)行了任何實(shí)際的寫(xiě)入和讀取操作辩越。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末集晚,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子区匣,更是在濱河造成了極大的恐慌偷拔,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亏钩,死亡現(xiàn)場(chǎng)離奇詭異莲绰,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)姑丑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)蛤签,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人栅哀,你說(shuō)我怎么就攤上這事震肮〕屏” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵戳晌,是天一觀(guān)的道長(zhǎng)鲫尊。 經(jīng)常有香客問(wèn)我,道長(zhǎng)沦偎,這世上最難降的妖魔是什么疫向? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮豪嚎,結(jié)果婚禮上搔驼,老公的妹妹穿的比我還像新娘。我一直安慰自己侈询,他們只是感情好舌涨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著扔字,像睡著了一般泼菌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上啦租,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音荒揣,去河邊找鬼篷角。 笑死,一個(gè)胖子當(dāng)著我的面吹牛系任,可吹牛的內(nèi)容都是我干的恳蹲。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼俩滥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼嘉蕾!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起霜旧,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤错忱,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后挂据,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體以清,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年崎逃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了掷倔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡个绍,死狀恐怖勒葱,靈堂內(nèi)的尸體忽然破棺而出浪汪,到底是詐尸還是另有隱情,我是刑警寧澤凛虽,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布死遭,位于F島的核電站,受9級(jí)特大地震影響涩维,放射性物質(zhì)發(fā)生泄漏殃姓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一瓦阐、第九天 我趴在偏房一處隱蔽的房頂上張望蜗侈。 院中可真熱鬧,春花似錦睡蟋、人聲如沸踏幻。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)该面。三九已至,卻和暖如春信卡,著一層夾襖步出監(jiān)牢的瞬間隔缀,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工傍菇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留猾瘸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓丢习,卻偏偏與公主長(zhǎng)得像牵触,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子咐低,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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