Java逆變與協(xié)變

引子

《Effective Java》中第25條中《列表優(yōu)于數(shù)組》中提到數(shù)組是協(xié)變的,相反泛型是不可變的

其實(shí)用于描述Java類(lèi)型轉(zhuǎn)換后的繼承關(guān)系一共有三種,協(xié)變,逆變,不可變

其定義為:

如果A、B表示類(lèi)型 f(?) 表示類(lèi)型轉(zhuǎn)換, 表示繼承關(guān)系(比如,A≤B 表示A是由B派生出來(lái)的子類(lèi));

  • f(?) 是逆變(contravariant)的,當(dāng)A≤B時(shí)有f(B)≤f(A)成立

  • f(?) 是協(xié)變(covariant)的,當(dāng)A≤B時(shí)有f(A)≤f(B)成立

  • f(?) 是不變(invariant)的,當(dāng)A≤B時(shí)上述兩個(gè)式子均不成立,即f(A)與f(B)相互之間沒(méi)有繼承關(guān)系

協(xié)變

數(shù)組是協(xié)變的,那就意味著String是Object的子類(lèi),則String[] 是 Object[]的子類(lèi),但是會(huì)有一個(gè)問(wèn)題:

    Object[] objArray = new Integer[1];
    objArray[0] = "a string";

這段代碼是合法的,但是在運(yùn)行時(shí)就會(huì)因?yàn)轭?lèi)型不符報(bào)錯(cuò)

不可變

泛型是不可變的,這意味著

    ArrayList<Object> objArray = new ArrayList<Object>();
    objArray.add("a string");

是無(wú)法通過(guò)編譯的。根據(jù)不可變的定義,ArrayList<Object> 和 ArrayList<String>沒(méi)有繼承關(guān)系

這樣的設(shè)計(jì)是為了保證類(lèi)型安全,根據(jù)《Effective Java》中的說(shuō)法:


    // Why generic array creation is illegal - won't compile
    List<String>[] stringLists = new ArrayList<String>[1]; // (1)
    List<Integer> intList = Arrays.asList(42);             // (2)
    Object[] objects = stringLists;                        // (3)
    objects[0] = intList;                                  // (4)
    String s = stringLists[0].get(0);                      // (5)

假設(shè)(1)是編譯正確的,那么在(5)的時(shí)候就必然會(huì)出現(xiàn)類(lèi)型不匹配,因?yàn)樗鼑L試把整型賦值給字符類(lèi)型的

泛型類(lèi)型中利用通配符(extends/super)來(lái)實(shí)現(xiàn)協(xié)變和逆變

List<? extends Fruit> 表明每個(gè)item是Fruit/Fruit的子類(lèi),這其實(shí)表明了泛型的上線(xiàn),實(shí)現(xiàn)了協(xié)變

同樣,List<? super Fruit> 表明每個(gè)item都是Fruit/Furit的基類(lèi),這表明了泛型的下線(xiàn),實(shí)現(xiàn)了逆變

泛型的協(xié)變/逆變使用依靠著一個(gè)PECS原則,即Provider Extends Consumer Super

還以L(fǎng)ist為例:

    // 前提為Apple為Fruit的派生子類(lèi)
    List<? extends Fruit> list = new ArrayList<Apple>();
    list.add(new Apple());

這樣的寫(xiě)法是無(wú)法通過(guò)編譯的,會(huì)提示類(lèi)型不符,因?yàn)?code>? extends Furit表明了Furit或者Furit的派生子類(lèi)。如果我們存入Apple,在get時(shí)強(qiáng)轉(zhuǎn)為Apple自然是不會(huì)有問(wèn)題,但是如果我存入Banana,同樣是水果,那么在get時(shí)就會(huì)報(bào)錯(cuò),這樣是類(lèi)型不安全的

泛型通過(guò)擦除來(lái)實(shí)現(xiàn)的,? extends Furit在編譯階段只是一個(gè)標(biāo)記,和數(shù)組具體化類(lèi)型是不一樣的

那么作為一個(gè)Consumer,應(yīng)當(dāng)使用super

   // 前提為Apple為Fruit的派生子類(lèi)
    List<? super Fruit> list = new ArrayList<Food>();
    list.add(new Apple());

這段代碼是編譯通過(guò)的,只要類(lèi)型要求是Fruit/Fruit的基類(lèi),那么存入的類(lèi)型必定可以強(qiáng)轉(zhuǎn)為Fruit/Fruit的基類(lèi),是類(lèi)型安全的

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容