《Thinking in Java》學(xué)習(xí)——15章泛型(二)

通配符

1.數(shù)組具有協(xié)變性:可以向?qū)С鲱愋偷臄?shù)組賦予基類型的數(shù)組引用:

class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}

class CovariantArrays {
    public static void main(String... args) {
        Fruit[] fruit = new Apple[10];
        fruit[0] = new Apple(); //OK
        fruit[1] = new Jonathan(); //OK
        fruit[2] = new Orange(); //ArrayStoreException
    }
}

上面的代碼不會(huì)出現(xiàn)編譯問(wèn)題席噩,因?yàn)?strong>Apple、OrangeJonathan都是Fruit的子類型,Fruit類型的引用持有它們并沒(méi)有任何問(wèn)題忧设,是有意義的。但是fruit[2] = new Orange();這一句在運(yùn)行時(shí)會(huì)拋出ArrayStoreException異常颠通,因?yàn)閿?shù)組fruit在運(yùn)行時(shí)的實(shí)際類型為Apple址晕。
2.數(shù)組的協(xié)變性對(duì)List并不起作用:

List<? extends Fruit> flist = new ArrayList<Apple>();
//Compile Error: can't add any type of object
//flist.add(new Apple());
//flist.add(new Fruit);
//flist.add(new Object());

上面代碼中唯一的限制就是這個(gè)List要持有某種具體的FruitFruit的子類型,但是編譯器實(shí)際上并不知道LIst持有什么類型顿锰,那么也就不能安全地向其中添加對(duì)象谨垃,因此會(huì)出現(xiàn)編譯時(shí)錯(cuò)誤。

一.編譯器有多聰明

1.雖然在上面的程序中Listadd()方法不可用硼控,但是并不是所有的方法都是不可用的:

public class CompilerIntelligence {
    public static main(String... args) {
        List<? extends Fruit> flist = Arrays.asList(new Apple());
        Apple a = (Apple) flist.get(0);
        flist.contains(new Apple());
        flist.indexOf(new Apple());
    }
}

這兩段程序的區(qū)別在于add()方法將接受一個(gè)具有泛型參數(shù)類型的參數(shù)刘陶,但是contains()indexOf()將返回或者接受Object類型的參數(shù)。因此淀歇,在指定一個(gè)ArrayList<? extends Fruit>時(shí)易核,add()的參數(shù)就變成了"? extends Fruit"匈织,此時(shí)浪默,編譯器并不能了解這里需要Fruit的哪個(gè)具體子類型,因此它不會(huì)接受任何類型的Fruit缀匕。而另外的方法使用了Object纳决,并不涉及通配符,因此編譯器也將允許這個(gè)調(diào)用乡小。而上面的get()方法只會(huì)也只能返回Fruit對(duì)象阔加,這是在該泛型參數(shù)所給定了邊界——“任何擴(kuò)展自Fruit的對(duì)象”之后所能做的唯一的事情了。

二.逆變

1.超類通配符:使用方法是由某個(gè)特定類的任何基類來(lái)界定的即<? super MyClass>满钟。
2.有了超類通配符之后胜榔,程序可以這樣寫(xiě):

List<? super Apple> apples = Arrays.asList(new Apple());
apples.add(new Apple());
apples.add(new Jonathan());
apples.add(new Fruit()); //Error

這里Apple是下接,那么向其中添加AppleApple的子類型是安全的湃番,但是添加Fruit是不安全的夭织。

三.無(wú)界通配符

1.第一種情況下無(wú)界通配符意味著“任何事物”,即編譯器很少關(guān)心使用的是原生類型還是<?>吠撮。因此尊惰,<?>是在聲明:我是想用Java的泛型來(lái)編寫(xiě)代碼,我在這里并不是要用原生類型,但是在當(dāng)前這種情況下弄屡,泛型參數(shù)可以持有任何類型题禀。
2.泛型的另一種應(yīng)用是:當(dāng)你在處理多個(gè)參數(shù)時(shí),優(yōu)勢(shì)允許一個(gè)參數(shù)可以時(shí)任喝類型膀捷,同時(shí)為其他參數(shù)確定某種特定類型迈嘹,如Map<String, ?> map = new HashMap<String, Integer>
3.List實(shí)際表示“持有任何Object類型的List”全庸,而List<?>表示“具有某種特定類型的非原生List江锨,只是我們不知道那種類型是什么「馄”
4.使用確切類型來(lái)代替通配符啄育,可以使用泛型參數(shù)來(lái)做更多的事,但是使用通配符使得你必須接受范圍更寬的參數(shù)化類型作為參數(shù)拌消。

一.捕獲轉(zhuǎn)換

1.捕捉轉(zhuǎn)換:如果向一個(gè)使用<?>的方法傳遞原生類型挑豌,那么對(duì)編譯器來(lái)說(shuō),可能會(huì)推斷出實(shí)際的類型參數(shù)墩崩,使得這個(gè)方法可以回轉(zhuǎn)并使用另一個(gè)使用這個(gè)確切類型的方法氓英。

public class CaptureConversion {
    static <T> void f1(Holder<T> holder) {
        T t = holder.get();
        System.out.println(t.getClass().getSimpleName());
    }
    static void f2(Holder<?> holder) {
        f1(holder);
    }
    @SupressWarnings("unchecked")
    public static void main(String... args) {
        Holder raw = new Holder<Integer>(1);
        //f1(raw); //warnings
        f2(raw); //no warnings
}

上面代碼的執(zhí)行過(guò)程是:參數(shù)類型在調(diào)用f2()的過(guò)程中被捕獲,因此它可以在對(duì)f1()對(duì)調(diào)用中被使用鹦筹。

問(wèn)題

一.任何基本類型都不能作為類型參數(shù)

1.不能將基本類型用作類型參數(shù)铝阐,因此不能創(chuàng)建ArrayList<int>之類的東西。
2.上一條的解決方法是使用基本類型的包裝器以及自動(dòng)包裝機(jī)制铐拐。比如徘键,創(chuàng)建一個(gè)ArrayList<Integer>,并將基本類型int應(yīng)用于這個(gè)容器遍蟋。
3.但是吹害,自動(dòng)包裝機(jī)制不能應(yīng)用于數(shù)組,因此當(dāng)需要使用數(shù)組的時(shí)候需要注意虚青。

二.實(shí)現(xiàn)參數(shù)化接口

1.一個(gè)類不能實(shí)現(xiàn)同一個(gè)泛型接口的兩種變體它呀,由于擦除的原因,這兩個(gè)變體會(huì)成為相同的接口:

interface Payable<T> {}

class Employee implements Payable<Employee> {}

class Hourly extends Employee implements Payable<Hourly> {}

由于擦除會(huì)將Payable<Employee>Payable<Hourly>簡(jiǎn)化為相同的類Payable棒厘,也就意味著上面的代碼重復(fù)兩次地實(shí)現(xiàn)了相同的接口纵穿,因此不能編譯。但是奢人,如果將泛型參數(shù)全都移除谓媒,上述代碼是可以編譯的。

三.轉(zhuǎn)型和警告

1.使用帶有泛型參數(shù)類型的轉(zhuǎn)型或instanceof不會(huì)帶有任何效果:

class Stack<T> {
    private int index = 0;
    private Object[] storage;
    ......
    @SupressWarnings("unchecked")
    public T pop() {
        return (T) storage[--index];
    }
}

pop()中达传,由于擦除的原因篙耗,編譯器無(wú)法知道這個(gè)轉(zhuǎn)型是否是安全的迫筑,并且由于T被擦除到它的第一個(gè)邊界,此處即Object宗弯,因此pop()實(shí)際上只是將Object轉(zhuǎn)型為Object脯燃,相當(dāng)于沒(méi)有執(zhí)行任何轉(zhuǎn)型。

四.重載
public class UseList<W, T> {
    void f(List<W> w) {}
    void f(List<T> t) {}
}

上面的代碼由于擦除的原因蒙保,重載的方法將產(chǎn)生相同的簽名辕棚,因此是不能編譯的。

五.基類劫持了接口
public class ComparablePet implements Comparable<ComparablePet> {
    public int compareTo(ComparablePet arg) {
        return 0;
    }
}

class Cat extends ComparablePet implements Comparable<Cat> {
    public int compareTo(Cat arg) {
        return 0;
    }
}

這里有一個(gè)可以進(jìn)行比較的Pet類邓厕,這里我們的想法是對(duì)它的子類Cat的比較盡興窄化逝嚎。但是會(huì)出現(xiàn)問(wèn)題,因?yàn)橐坏?strong>Comparable確定了ComparablePet參數(shù)详恼,那么其他任何實(shí)現(xiàn)類都不能與ComparablePet之外的任何對(duì)象進(jìn)行比較补君。

自限定的類型

一.古怪的循環(huán)泛型

1.不能直接集成一個(gè)泛型參數(shù),但是昧互,可以繼承在自己的定義中使用這個(gè)泛型參數(shù)的類挽铁,如:

class GenericType<T> {}

public class CuriouslyRecurringGener extends GenericType<CuriousRecurringGener> {}

這就是古怪的循環(huán)泛型(CRG):類相當(dāng)古怪地出現(xiàn)在它自己基類中這一事實(shí)。
2.CRG的本質(zhì):基類用導(dǎo)出類替代其參數(shù)敞掘。這意味著泛型基類變成了一種其所有導(dǎo)出類的公共功能的模版叽掘,但是這些功能對(duì)于其所有參數(shù)和返回值,將使用導(dǎo)出類型:

class BasicHolder<T> {
    T element;
    void set(T arg) {
        this.element = arg;
    }
    T get() {
        return element;
    }
}

class SubType extends BasicHolder<SubType> {
    public static void main(String... args) {
        SubType st1 = new SubType(), st2 = new SubType();
        st1.set(st2);
        SubType st2 = st1.get();
    }
} 
二.自限定

1.自限定:

class SelfBounded<T extends SelfBounded<T>> {}

SelfBounded類接受泛型參數(shù)T玖雁,而T由一個(gè)邊界限定更扁,這個(gè)邊界就是擁有T作為其參數(shù)的SelfBounded
2.自限定強(qiáng)制泛型當(dāng)作自己的邊界參數(shù)來(lái)使用赫冬。如:

class A extends SelfBounded<A> {}

3.自限定限制只能強(qiáng)制作用于繼承關(guān)系浓镜。如果食用自限定,就應(yīng)該了解這個(gè)類所用的類型參數(shù)將與使用這個(gè)參數(shù)的類具有相同的基類型面殖。這會(huì)強(qiáng)制要求使用這個(gè)類的每個(gè)人都要遵循這種形式竖哩。

三.參數(shù)協(xié)變

1.自限定類型的價(jià)值在于它們可以產(chǎn)生協(xié)變參數(shù)類型——方法參數(shù)類型后隨子類而變化:

interface Generic<T extends Generic<T>> {
    T get();
    void set(T t);
}

interface GetterAndSetter extends Generic<GetterAndSetter> {}

public class Generics {
    void test(Getter g) {
        GetterAndSetter result = g.get();
        Generic g = g.get();
        GetterAndSetter gns = new GetterAndSetter();
        gns.set(result);
        gns.set(g); // Can't compile
    }
}

上面代碼中resultgg的實(shí)際類型都是GetterAndSetter,同時(shí)脊僚,set()方法只能接受GetterAndSetter類型的參數(shù),如果傳入基類參數(shù)遵绰,編譯不會(huì)通過(guò)辽幌。但是注意,這段代碼由于使用了自限定類型產(chǎn)生子類類型相同的換回類型椿访,只有在囊括了協(xié)變返回類型的Java SE5的環(huán)境下才能編譯乌企。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市成玫,隨后出現(xiàn)的幾起案子加酵,更是在濱河造成了極大的恐慌拳喻,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猪腕,死亡現(xiàn)場(chǎng)離奇詭異冗澈,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)陋葡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)亚亲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人腐缤,你說(shuō)我怎么就攤上這事捌归。” “怎么了岭粤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵惜索,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我剃浇,道長(zhǎng)门扇,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任偿渡,我火速辦了婚禮臼寄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘溜宽。我一直安慰自己吉拳,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布适揉。 她就那樣靜靜地躺著留攒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嫉嘀。 梳的紋絲不亂的頭發(fā)上炼邀,一...
    開(kāi)封第一講書(shū)人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音剪侮,去河邊找鬼拭宁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瓣俯,可吹牛的內(nèi)容都是我干的杰标。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼彩匕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼腔剂!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起驼仪,我...
    開(kāi)封第一講書(shū)人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤掸犬,失蹤者是張志新(化名)和其女友劉穎袜漩,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體湾碎,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宙攻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了胜茧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片粘优。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖呻顽,靈堂內(nèi)的尸體忽然破棺而出雹顺,到底是詐尸還是另有隱情,我是刑警寧澤廊遍,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布嬉愧,位于F島的核電站,受9級(jí)特大地震影響喉前,放射性物質(zhì)發(fā)生泄漏没酣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一卵迂、第九天 我趴在偏房一處隱蔽的房頂上張望裕便。 院中可真熱鬧,春花似錦见咒、人聲如沸偿衰。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)下翎。三九已至,卻和暖如春宝当,著一層夾襖步出監(jiān)牢的瞬間视事,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工庆揩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留俐东,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓盾鳞,卻偏偏與公主長(zhǎng)得像犬性,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子腾仅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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

  • 第8章 泛型 通常情況的類和函數(shù),我們只需要使用具體的類型即可:要么是基本類型套利,要么是自定義的類推励。但是在集合類的場(chǎng)...
    光劍書(shū)架上的書(shū)閱讀 2,143評(píng)論 6 10
  • 前言 泛型(Generics)的型變是Java中比較難以理解和使用的部分验辞,“神秘”的通配符稿黄,讓我看了幾遍《Java...
    珞澤珈群閱讀 7,791評(píng)論 12 51
  • 2.簡(jiǎn)單泛型 -********Java泛型的核心概念:告訴編譯器想使用什么類型, 然后編譯器幫你處理一切細(xì)節(jié) 2...
    CodingHou閱讀 389評(píng)論 0 0
  • 定義 逆變與協(xié)變用來(lái)描述類型轉(zhuǎn)換(type transformation)后的繼承關(guān)系,其定義:如果A跌造、B表示類型...
    開(kāi)發(fā)者小王閱讀 25,624評(píng)論 4 61
  • [TOC] 深入理解 Java 泛型 概述 泛型的本質(zhì)是參數(shù)化類型杆怕,通常用于輸入?yún)?shù)、存儲(chǔ)類型不確定的場(chǎng)景壳贪。相比于...
    albon閱讀 5,302評(píng)論 0 7