泛型 - 通配符

使用通配符的原因:Java中的數(shù)組是協(xié)變的山害,但是泛型不支持協(xié)變阿蝶。

數(shù)組的協(xié)變

首先了解下什么是數(shù)組的協(xié)變晴圾,看下面的例子:

Number[] nums = new Integer[10]; // OK

因為Integer是Number的子類,一個Integer對象也是一個Number對象好啰,所以一個Integer的數(shù)組也是一個Number的數(shù)組轩娶,這就是數(shù)組的協(xié)變。

Java把數(shù)組設(shè)計成協(xié)變的框往,在一定程度上是有缺陷的鳄抒。因為盡管把Integer[]賦值給Number[],Integer[]可以向上轉(zhuǎn)型為Number[]椰弊,但是數(shù)據(jù)元素的實際類型是Integer许溅,只能向數(shù)組中放入Integer或者Integer的子類。如果向數(shù)組中放入Number對象或者Number其他子類的對象秉版,對于編譯器來說也是可以通過編譯的贤重。但是運行時JVM能夠知道數(shù)組元素的實際類型是Integer,當(dāng)其它對象加入數(shù)組是就會拋出異常(java.lang.ArrayStoreException)清焕。

泛型的設(shè)計目的之一就是保證了類型安全并蝗,讓這種運行時期的錯誤在編譯期就能發(fā)現(xiàn),所以泛型是不支持協(xié)變的秸妥。例如:

List<Number> nums = new ArrayList<Integer>(); // incompatible types

當(dāng)確實需要建立這種向上轉(zhuǎn)型的類型關(guān)系的時候滚停,就需要用到泛型的通配符特性了。例如:

List<? extends Number> nums = new ArrayList<Integer>(); // OK

無邊界通配符(Unbounded Wildcards)

語法:

class-name<?> var-name

例子:

public static void print(List<?> list) {
    for (Object obj : list) {
        System.out.println(o);
    }
}

List<?> list和List list的區(qū)別:

  • List<?> list是表示持有某種特定類型對象的List粥惧,但是不知道是哪種類型键畴;List list是表示持有Object類型對象的List。
  • List<?> list因為不知道持有的實際類型突雪,所以不能add任何類型的對象起惕,但是List list因為持有的是Object類型對象,所以可以add任何類型的對象咏删。
    注意:List<?> list可以add(null)惹想,因為null是任何引用數(shù)據(jù)類型都具有的元素。

上邊界限定的通配符(Upper Bounded Wildcards)

語法:

class-name<? extends superclass> var-name

例子:

public static double sum(List<? extends Number> list) {
    double s = 0.0;
    for (Number num : list) {
        s += num.doubleValue();
    }
    
    return s;
}

List<? extends Number> list = new ArrayList<Integer>(); // OK
List<? extends Number> list = new ArrayList<Object>(); // error

特性:

  • List<? extends Number> list表示某種特定類型(Number或者Number的子類)對象的List督函。跟無邊界通配符一樣勺馆,因為無法確定持有的實際類型,所以這個List也不能add除null外的任何類型的對象侨核。
list.add(new Integer(1)); // error
list.add(null); // OK
  • 從list中獲取對象是是可以的(比如get(0)),因為在這個List中灌灾,不管實際類型是什么搓译,但肯定都能轉(zhuǎn)型為Number。
Number n = list.get(0); // OK
Integer i = list.get(0); // error
  • 事實上锋喜,只要是形式參數(shù)有使用類型參數(shù)的方法些己,在使用無邊界或者上邊界限定的通配符的情況下豌鸡,都不能調(diào)用。比如以java.util.ArrayList為例:
public E get(int index) // 可以調(diào)用
public int indexOf(Object o) // 可以調(diào)用
public boolean add(E e) // 不能調(diào)用

下邊界限定的通配符(Lower Bounded wildcards)

語法:

class-name<? super subclass> var-name

例子:

public static void writeTo(List<? super Integer> list) {
    // ...
}

List<? super Number> list = new ArrayList<Number>(); // OK
List<? super Number> list = new ArrayList<Object>(); // OK
List<? super Number> list = new ArrayList<Integer>(); // error

特性:

  • List<? super Integer> list表示某種特定類型(Integer或者Integer的父類)對象的List段标⊙墓冢可以確定這個List持有的對象類型肯定是Integer或者其父類,所以往list里面add一個Integer或者其子類的對象是安全的逼庞,因為Integer或者其子類的對象都可以向上轉(zhuǎn)型為Integer的父類對象蛇更。但是因為無法確定實際類型,所以往list里面add一個Integer的父類對象是不安全的赛糟。
list.add(new Integer(1)); // OK
list.add(new Object()); // error
  • 當(dāng)從List<? super Integer> list獲取具體的數(shù)據(jù)的時候派任,JVM在編譯的時候知道實際類型可以是任何Integer的父類,所以為了安全起見璧南,要用一個最頂層的父類對象來指向取出的數(shù)據(jù)掌逛,這樣就可以避免發(fā)生強制類型轉(zhuǎn)換異常了。
Object obj = list.get(0); // OK
Integer i = list.get(0); // error

PECS原則(Producer Extends Consumer Super)

從上面上邊界限定的通配符和下邊界限定的通配符的特性司倚,可以知道:

  • 對于上邊界限定的通配符豆混,無法向其中加入任何對象,但是可以從中正常取出對象动知。
  • 對于下邊界限定的通配符皿伺,可以存入subclass對象或者subclass的子類對象,但是取出時只能用Object類型變量指向取出的對象拍柒。

簡而言之心傀,上邊界限定(extends)的通配符適合于內(nèi)容的獲取,而下邊界限定(super)的通配符更適合于內(nèi)容的存入拆讯。所以就有了一個PECS原則來很好的解釋這兩種通配符的使用原則脂男。

  • 當(dāng)一個數(shù)據(jù)結(jié)構(gòu)作為producer對外提供數(shù)據(jù)的時候,應(yīng)該只能取數(shù)據(jù)而不能存數(shù)據(jù)种呐,所以適合使用上邊界限定(extends)的通配符宰翅。
  • 當(dāng)一個數(shù)據(jù)結(jié)構(gòu)作為consumer獲取并存入數(shù)據(jù)的時候,應(yīng)該只能存數(shù)據(jù)而不能取數(shù)據(jù)爽室,所以適合使用下邊界限定(super)的通配符汁讼。
  • 如果既需要取數(shù)據(jù)也需要存數(shù)據(jù),就不適合使用泛型的通配符阔墩。
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    for (int i = 0; i < src.size(); i++) {
        dest.set(i, src.get(i));
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嘿架,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子啸箫,更是在濱河造成了極大的恐慌耸彪,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忘苛,死亡現(xiàn)場離奇詭異蝉娜,居然都是意外死亡唱较,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門召川,熙熙樓的掌柜王于貴愁眉苦臉地迎上來南缓,“玉大人,你說我怎么就攤上這事荧呐『盒危” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵坛增,是天一觀的道長获雕。 經(jīng)常有香客問我,道長收捣,這世上最難降的妖魔是什么届案? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮罢艾,結(jié)果婚禮上楣颠,老公的妹妹穿的比我還像新娘。我一直安慰自己咐蚯,他們只是感情好童漩,可當(dāng)我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著春锋,像睡著了一般矫膨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上期奔,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天侧馅,我揣著相機與錄音,去河邊找鬼呐萌。 笑死馁痴,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肺孤。 我是一名探鬼主播罗晕,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼赠堵!你這毒婦竟也來了小渊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤茫叭,失蹤者是張志新(化名)和其女友劉穎酬屉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杂靶,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡梆惯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吗垮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片垛吗。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖烁登,靈堂內(nèi)的尸體忽然破棺而出怯屉,到底是詐尸還是另有隱情,我是刑警寧澤饵沧,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布锨络,位于F島的核電站,受9級特大地震影響狼牺,放射性物質(zhì)發(fā)生泄漏羡儿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一是钥、第九天 我趴在偏房一處隱蔽的房頂上張望掠归。 院中可真熱鬧,春花似錦悄泥、人聲如沸虏冻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厨相。三九已至,卻和暖如春鸥鹉,著一層夾襖步出監(jiān)牢的瞬間蛮穿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工宋舷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留绪撵,地道東北人。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓祝蝠,卻偏偏與公主長得像音诈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绎狭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,514評論 2 348

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