Java 泛型 PECS - 生產(chǎn)者extends消費(fèi)者super

https://www.kancloud.cn/apachecn/howtodoinjava-zh/1952924

昨天,我正在研究一些 Java 集合 API,并且發(fā)現(xiàn)了兩種主要用于將元素添加到集合中的方法挪哄。 他們倆都使用泛型語法來獲取方法參數(shù)棘街。 但是鼓寺,第一種方法是使用<? super T>配并,而第二種方法是使用<? extends E>光酣。 為什么勾缭?

首先揍障,讓我們看一下這兩種方法的完整語法。

此方法負(fù)責(zé)將集合c的所有成員添加到另一個(gè)調(diào)用此方法的集合中俩由。

boolean addAll(Collection<? extends E> c);

調(diào)用此方法可將“元素”添加到集合c毒嫡。

public static <T> boolean addAll(Collection<? super T> c, T... elements);

兩者似乎都在做簡單的事情,所以為什么它們都有不同的語法幻梯。 我們許多人可能會(huì)納悶兜畸。 在這篇文章中,我試圖揭開圍繞它的概念的神秘性碘梢,該概念主要被稱為 PECS (最早由 Joshua Bloch 在他的著作《Effective Java》中創(chuàng)造的術(shù)語)咬摇。

為什么要使用泛型通配符?
在我與 java 泛型 相關(guān)的上一篇文章中煞躬,我們了解到肛鹏,泛型本質(zhì)上用于類型安全性和不變式。 用例可以是 Integer 的列表恩沛,即List<Integer>在扰。 如果您在 Java 中聲明List<Integer>之類的列表,則 Java 保證它將檢測并報(bào)告您將任何非整數(shù)類型插入上述列表的任何嘗試雷客。

但是很多時(shí)候芒珠,我們面臨這樣的情況,為了特定的目的搅裙,我們必須在方法中傳遞類的子類型或超類型作為參數(shù)皱卓。 在這些情況下,我們必須使用協(xié)變(縮小引用)和逆變(擴(kuò)大引用)之類的概念部逮。

了解<? extends T>
這是 PECS 的第一部分娜汁,即 PE(生產(chǎn)者extends)。 為了將其與現(xiàn)實(shí)生活中的術(shù)語聯(lián)系起來甥啄,讓我們使用一籃子水果(即水果的集合)的類比存炮。 當(dāng)我們從籃子里摘水果時(shí),我們要確保只取出水果而沒有其他東西。 這樣我們就可以編寫如下泛型代碼:

Fruit get = fruits.get(0);
在上述情況下穆桂,我們需要將水果的集合聲明為List<? extends Fruit>宫盔。 例如:

class Fruit {
   @Override
   public String toString() {
      return "I am a Fruit !!";
   }
}

class Apple extends Fruit {
   @Override
   public String toString() {
      return "I am an Apple !!";
   }
}

public class GenericsExamples
{
   public static void main(String[] args)
   {
      //List of apples
      List<Apple> apples = new ArrayList<Apple>();
      apples.add(new Apple());

      //We can assign a list of apples to a basket of fruits;
      //because apple is subtype of fruit 
      List<? extends Fruit> basket = apples;

      //Here we know that in basket there is nothing but fruit only
      for (Fruit fruit : basket)
      {
         System.out.println(fruit);
      }

      //basket.add(new Apple()); //Compile time error
      //basket.add(new Fruit()); //Compile time error
   }
}

查看上面的for循環(huán)。 它確保了從籃子里出來的任何東西都肯定會(huì)結(jié)出果實(shí)享完; 因此灼芭,您可以遍歷它,然后簡單地將其澆鑄成水果般又。 現(xiàn)在彼绷,在最后兩行中,我嘗試在購物籃中添加Apple和Fruit茴迁,但是編譯器不允許我添加寄悯。 為什么?

如果我們考慮一下堕义,原因很簡單猜旬。 <? extends Fruit>通配符告訴編譯器我們正在處理水果類型的子類型,但是我們無法知道哪個(gè)水果倦卖,因?yàn)榭赡艽嬖诙鄠€(gè)子類型洒擦。 由于沒有辦法說出來,而且我們需要保證類型安全(不變性)怕膛,因此您不能在此類結(jié)構(gòu)內(nèi)放置任何內(nèi)容熟嫩。

另一方面,由于我們知道它可能是Fruit的子類型褐捻,因此可以從結(jié)構(gòu)中獲取數(shù)據(jù)掸茅,并保證它是Fruit。

在上面的示例中舍扰,我們從集合List<? extends Fruit> basket中取出元素倦蚪。所以這個(gè)籃子實(shí)際上是在生產(chǎn)水果。 簡而言之边苹,當(dāng)您只想從集合中檢索元素時(shí),請將其視為生產(chǎn)者并使用<? extends T>語法裁僧。 “生產(chǎn)者extends”現(xiàn)在對您來說更有意義个束。

了解<? super T>
現(xiàn)在以不同的方式查看上述用例。 假設(shè)我們正在定義一個(gè)方法聊疲,在此方法中茬底,我們只會(huì)在此購物籃中添加不同的水果。 就像我們在帖子“ addAll(Collection<? super T> c, T... elements)”開頭看到的方法一樣获洲。 在這種情況下阱表,籃子用于存儲(chǔ)元素,因此應(yīng)稱為元素的使用者。

現(xiàn)在看下面的代碼示例:

class Fruit {
   @Override
   public String toString() {
      return "I am a Fruit !!";
   }
}

class Apple extends Fruit {
   @Override
   public String toString() {
      return "I am an Apple !!";
   }
}

class AsianApple extends Apple {
   @Override
   public String toString() {
      return "I am an AsianApple !!";
   }
}

public class GenericsExamples
{
   public static void main(String[] args)
   {
      //List of apples
      List<Apple> apples = new ArrayList<Apple>();
      apples.add(new Apple());

      //We can assign a list of apples to a basket of apples
      List<? super Apple> basket = apples;

      basket.add(new Apple());      //Successful
      basket.add(new AsianApple()); //Successful
      basket.add(new Fruit());      //Compile time error
   }
}

我們可以在籃子內(nèi)添加蘋果最爬,甚至是亞洲蘋果涉馁,但不能在籃子中添加Fruit(蘋果的超類型)。 為什么爱致?

原因是購物籃是對Apple的超類商品列表的引用烤送。 同樣,我們不知道它是是哪個(gè)超類型糠悯,但是我們知道可以將Apple及其任何子類型(它們是Fruit的子類型)添加為沒有問題(您可以隨時(shí)在超類的集合中添加一個(gè)子類)帮坚。 因此,現(xiàn)在我們可以在購物籃中添加任何類型的Apple互艾。

如何從這種類型的數(shù)據(jù)中獲取數(shù)據(jù)呢试和? 事實(shí)證明,您唯一可以使用的是Object實(shí)例:由于我們無法知道它是哪個(gè)超類型纫普,因此編譯器只能保證它將是對Object的引用灰署,因?yàn)镺bject是任何 Java 類型的超類型。

在上面的示例中局嘁,我們將元素放入集合List<? super Apple> basket溉箕; 所以在這里,這個(gè)籃子實(shí)際上是在消耗蘋果等元素悦昵。 簡而言之肴茄,當(dāng)您只想在集合中添加元素時(shí),請將其視為使用者并使用<? super T>語法但指。 現(xiàn)在寡痰,“消費(fèi)者super”對您也應(yīng)該更有意義。

總結(jié)
基于上述推理和示例棋凳,讓我們總結(jié)要點(diǎn)拦坠。

如果需要從集合中檢索類型T的對象,請使用<? extends T>通配符剩岳。
如果需要將T類型的對象放入集合中贞滨,請使用<? super T>通配符。
如果您需要同時(shí)滿足這兩個(gè)條件拍棕,請不要使用任何通配符晓铆。 就這么簡單。
簡而言之绰播,請記住術(shù)語 PECS骄噪。 生產(chǎn)者extends消費(fèi)者super。 真的很容易記住蠢箩。
這就是 Java 中泛型中簡單而又復(fù)雜的概念的全部链蕊。 通過評論讓我知道您的想法事甜。

祝您學(xué)習(xí)愉快!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末滔韵,一起剝皮案震驚了整個(gè)濱河市逻谦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌奏属,老刑警劉巖跨跨,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異囱皿,居然都是意外死亡勇婴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門嘱腥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耕渴,“玉大人,你說我怎么就攤上這事齿兔〕髁常” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵分苇,是天一觀的道長添诉。 經(jīng)常有香客問我,道長医寿,這世上最難降的妖魔是什么栏赴? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮靖秩,結(jié)果婚禮上须眷,老公的妹妹穿的比我還像新娘。我一直安慰自己沟突,他們只是感情好花颗,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著惠拭,像睡著了一般扩劝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上求橄,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天今野,我揣著相機(jī)與錄音,去河邊找鬼罐农。 笑死,一個(gè)胖子當(dāng)著我的面吹牛催什,可吹牛的內(nèi)容都是我干的涵亏。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼气筋!你這毒婦竟也來了拆内?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤宠默,失蹤者是張志新(化名)和其女友劉穎麸恍,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搀矫,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抹沪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瓤球。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片融欧。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖卦羡,靈堂內(nèi)的尸體忽然破棺而出噪馏,到底是詐尸還是另有隱情,我是刑警寧澤绿饵,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布欠肾,位于F島的核電站,受9級特大地震影響拟赊,放射性物質(zhì)發(fā)生泄漏刺桃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一要门、第九天 我趴在偏房一處隱蔽的房頂上張望虏肾。 院中可真熱鬧,春花似錦欢搜、人聲如沸封豪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吹埠。三九已至,卻和暖如春疮装,著一層夾襖步出監(jiān)牢的瞬間缘琅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工廓推, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留刷袍,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓樊展,卻偏偏與公主長得像呻纹,于是被迫代替她去往敵國和親堆生。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

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