Java泛型食用筆記(四) -- 通配符

Java泛型食用筆記(四) -- 通配符


1. 三種通配符

通配符為一個泛型類所指定的類型集合提供了一個有用的類型范圍催首,Java 里有三種通配符:

  • 無限定通配符蚁趁, <?>
  • 上界限定符裙盾, <? extends Number>
  • 下界限定符, <? super Number>

上界限定符接受 extends 后面類的本身與其子類他嫡, 下界限定符接受 super 后面類的本身與其父類番官。無限定通配符接受任何類。

2. 無限定通配符

無限定通配符表示匹配任意類涮瞻。ArrayList<?>ArrayList鲤拿,ArrayList<Object> 看上去功能有點類似假褪,但實際卻不一樣署咽。 ArrayList<?> 是任意 ArrayList<T> 的超類,而我們知道 ArrayList<Object> 并不是生音。ArrayList<?> 雖然可以匹配任何類位他,我們并不知道那個類的類型悄蕾,但我們知道里面的所有元素都有相同的類。而原始類 ArrayList 可以添加任意不同類型的元素,編譯器并不能進(jìn)行類型判斷纽疟,但運(yùn)行的時候可能會拋出異常。ArrayList<Object> 明確的告訴我們可以添加任何類型的對象烂琴。

看一個無限定通配符例子

public class GenericTest07 {
    public static void printCollection(Collection<Object> col1) {
        for (Object obj : col1) {
            System.out.println(obj);
        }
    }

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        printCollection(list);  // compile error
    }
}

由于 ArrayList<Integer> 不是 Collection<Object> 的子類弧械,故編譯不能通過。改用無限定通配符即可編譯通過:

public class GenericTest07 {
    public static void printCollection(Collection<?> col1) {
        for (Object obj : col1) {
            System.out.println(obj);
        }
    }

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        printCollection(list);  
    }
}

Collection<?> 是任意 T ArrayList<T> 的超類譬挚,可以編譯通過锅铅。但是,在printCollection 方法中减宣,卻不能使用任何帶有類型參數(shù)的方法盐须,比如 add(T t), 而 remove(Object obj) contains(Object obj) 等方法都是可以調(diào)用的。

再看一個例子:

public class GenericTest08 {
    public static void reBox(Box<?> box) {
        System.out.println(box.put(box.get()));
    }
}

public interface Box<T> {
    public T get();
    public void put(T t);
}

這段代碼初看應(yīng)該是 work 的漆腌,然而事實可能要讓你失望了贼邓,編譯會提示類型不兼容的錯誤阶冈。事實上,你根本就不能在此處調(diào)用 box.put() 方法塑径,因為box.put() 的形參類型是未知的女坑,編譯器不能檢驗?zāi)愕膶崊㈩愋褪欠衽c形參類型一致。

有沒有辦法實現(xiàn)這個邏輯的晓勇,我們需要借助一個輔助方法堂飞。

public class GenericTest08 {
    public static void reBox(Box<?> box) {
        reboxHelper(box);
    }

    private static <V> void reboxHelper(Box<V> box) {
        box.put(box.get());
    }
}

reboxHelper 方法幫助編譯器保留一部分類型信息。

上界限定符

我們知道绑咱,和數(shù)組不一樣绰筛,泛型并不是協(xié)變的。比如 DogAnimal 的子類描融,那 Dog[] 也是 Animal[] 的子類铝噩,但 List<Dog> 并不是 List<Animal> 的子類。這個時候上界限定符的作用就體現(xiàn)出來了窿克,寫法是 List<? extends Animal>,可以解釋為“可以放入任何 Animal 及其子類的列表”骏庸,之前講到泛型編譯后會抹除類型參數(shù)成 Object 類型,當(dāng)你使用上界限定符后年叮,就抹除成上界具被。

看代碼

public class GenericTest {
    public static void main(String[] args) {
        ArrayList<Integer> holder = new ArrayList<Integer>();
        holder.add(new Integer(1));

        // ArrayList<Number> numHolder = holder;  // 1. compile failed. ArrayList<Number> 不是 ArrayList<Integer> 的父類
        ArrayList<? extends Number> numHolder = holder; // 2. ok
        Number num = numHolder.get(0);  // 3. ok. return Number
        numHolder.contains(new Integer(2)); // 4. ok
        // numHolder.add(new Integer(2));  // 5. compile error
    }
}

1 處編譯錯誤是因為泛型不是協(xié)變;Integer 是 Number 子類只损,故 2 處正確一姿;3 處正確,正如上段所說跃惫,上界限定符抹除成上屆叮叹,返回類型為 Number;4 處 ok爆存,是因為 contains 方法接受的是 Object 類蛉顽。5 處需要稍微注意,add 的形參類型也是 <? extends Number>先较,編譯器只能知道類型是 Number 的子類携冤,并不能確定具體類型是什么,因此無法驗證類型的安全性闲勺。

下界限定符

下界限定符也稱為超類通配符曾棕,寫法是 List<? super Dog>, 列表可以接受 Dog 類型及其父類對象。
上一接的第 5 處霉翔,如果向往里添加元素睁蕾,需要使用下界限定符。

public void writeTo(List<? super Integer> list) {
    list.add(new Integer(2));  // ok
}

<? super Integer> 說明類型參數(shù)一定是 Integer 的父類。因此向里添加 Integer 類或其子類的對象一定是安全的子眶。

PECS 原則

根據(jù)上面的例子瀑凝,我們看到,使用上界限定符定義的類臭杰,可以向外提供東西粤咪,也就是說作為 Producer。使用下界限定符定義的類渴杆,可以作為 Consumer 接收外部往自身添加?xùn)|西寥枝。

總結(jié)起來就是, "Producer Extends, Consumer Super":

  • "Producer Extends" - 如果你需要一個只讀類型磁奖,用它來produce T囊拜,那么使用<? extends T>
  • "Consumer Super" - 如果你需要一個只寫類型,用它來consume T比搭,那么使用<? super T>
  • 如果需要同時讀取以及寫入冠跷,那么我們就不能使用通配符了

下面一個方法同時涉及了這兩條規(guī)則。

public class Collections {
    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閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異穴亏,居然都是意外死亡蜂挪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進(jìn)店門迫肖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锅劝,“玉大人攒驰,你說我怎么就攤上這事蟆湖。” “怎么了玻粪?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵隅津,是天一觀的道長。 經(jīng)常有香客問我劲室,道長伦仍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任很洋,我火速辦了婚禮充蓝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己谓苟,他們只是感情好官脓,可當(dāng)我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涝焙,像睡著了一般卑笨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仑撞,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天赤兴,我揣著相機(jī)與錄音,去河邊找鬼隧哮。 笑死桶良,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沮翔。 我是一名探鬼主播艺普,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鉴竭!你這毒婦竟也來了歧譬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤搏存,失蹤者是張志新(化名)和其女友劉穎瑰步,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體璧眠,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡缩焦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年责静,在試婚紗的時候發(fā)現(xiàn)自己被綠了袁滥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡灾螃,死狀恐怖题翻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情腰鬼,我是刑警寧澤嵌赠,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站熄赡,受9級特大地震影響姜挺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜彼硫,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一炊豪、第九天 我趴在偏房一處隱蔽的房頂上張望凌箕。 院中可真熱鬧,春花似錦词渤、人聲如沸陌知。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仆葡。三九已至,卻和暖如春志笼,著一層夾襖步出監(jiān)牢的瞬間沿盅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工纫溃, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留腰涧,地道東北人。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓紊浩,卻偏偏與公主長得像窖铡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子坊谁,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,860評論 2 361

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