昨天,我正在研究一些 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í)愉快!