在上篇《JAVA泛型——基本使用》這篇文章中遺留以下問題,即將子類型Table或者也能添加到父類型Auction的泛型中,要實現這種功能必須借助於協變。
實驗准備
現在在《JAVA泛型——基本使用》一文展示的Decorator類型的基礎上,增加一些代碼,如代碼清單1所示。
代碼清單1
/**
*
* 描 述:Exp2使用br/>
* 作 者:jiaan.gja<br/>
* 歷 史: (版本) 作者 時間 注釋 <br/>
* @param itemList
*/
public void doDecorate2(List<? extends T> itemList) {
for(int i = 0; i < itemList.size(); i++) {
System.out.println(itemList.get(i));
}
}
/**
*
* 描 述:Exp2使用<br/>
* 作 者:jiaan.gja<br/>
* 歷 史: (版本) 作者 時間 注釋 <br/>
* @param itemList
* @param t
*/
public void addDecorate2(List<? extends T> itemList, T t) {
// itemList.add(t);
}
可以看到這跟《JAVA泛型——基本使用》中的doDecorate和addDecorate方法幾乎一樣,唯一的區別是將List<T>改為List<? extends T>,這種語法被稱為協變。如果你已經在IDE里寫了上面的代碼,肯定發現addDecorate2中的語句itemList.add(t);已經編譯報錯。所以我們先將itemList.add(t);這條語句暫時注釋掉,先來看看doDecorate2有沒有達到我們的預期。
實驗:泛型協變
現在我們看看協變的用途,即調用doDecorate2方法,如代碼清單2所示。
代碼清單2
/**
*
* 類 名: Exp2<br/>
* 描 述: 泛型的協變使用<br/>
* 作 者: jiaan.gja<br/>
* 創 建: 2015年8月20日<br/>
*
* 歷 史: (版本) 作者 時間 注釋
*/
class Exp2 {
public static void main(String[] args) {
Decorator<Auction> decoratorA = new Decorator<Auction>();
List<Auction> listA = new ArrayList<Auction>();
Auction auctionOne = new Auction("auctionOne");
Auction auctionTwo = new Auction("auctionTwo");
decoratorA.addDecorate2(listA, auctionOne);
decoratorA.addDecorate2(listA, auctionTwo);
decoratorA.doDecorate2(listA);
Decorator<Table> decoratorB = new Decorator<Table>();
List<Table> listB = new ArrayList<Table>();
Table tableOne = new Table("tableOne", 10);
Table tableTwo = new Table("tableTwo", 20);
decoratorB.addDecorate2(listB, tableOne);
decoratorB.addDecorate2(listB, tableTwo);
decoratorB.doDecorate2(listB);
Decorator<Service> decoratorC = new Decorator<Service>();
List<Service> listC = new ArrayList<Service>();
Service serviceOne = new Service("serviceOne", "methodOne");
Service serviceTwo = new Service("serviceTwo", "methodTwo");
decoratorC.addDecorate2(listC, serviceOne);
decoratorC.addDecorate2(listC, serviceTwo);
decoratorC.doDecorate2(listC);
/*
* 協變測試開始
*/
decoratorA.doDecorate2(listB);
decoratorA.doDecorate2(listC);
}
}
首先,我們看到doDecorate2方法至少能達到《JAVA泛型——基本使用》文中的doDecorate方法的效果。
由於聲明了decoratorA的類型是Decorator<Auction>,那么此時實例decoratorA的doDecorate2方法的參數可以認為是下面這樣:
List<? extends Auction> itemList
而listB的類型是ArrayList<Table>,listC的類型是ArrayList<Service>,也就是說協變允許:
List<? extends Auction> itemList = new ArrayList<Auction>(); List<? extends Auction> itemList = new ArrayList<Table>(); List<? extends Auction> itemList = new ArrayList<Service>();
這說明itemList中既可以存儲Auction,也可以存儲Table和Service。我們做個假設,當你想從itemList中獲取Table的時候,Java編譯器懷疑這實際可能是個Service,因此會有編譯錯誤。如果想從itemList中獲取Auction是沒有問題的,因為無論是Table還是Service它們都可以被自動轉換為父類Auction。這些推測,可以通過代碼清單3驗證。
代碼清單3
List<? extends Auction> itemList = listA;
Auction a = itemList.get(0);
Table t = itemList.get(0);
Service s = itemList.get(0);
itemList = listB;
a = itemList.get(0);
t = itemList.get(0);
s = itemList.get(0);
itemList = listC;
a = itemList.get(0);
t = itemList.get(0);
s = itemList.get(0);
同樣的道理,當你想要向itemList中添加Table時,編譯器懷疑itemList實際是個ArrayList<Service>,因此是不允許的。在獲取Auction時是沒有問題的,那么添加Auction會不會也可以?由於itemList可能是ArrayList<Service>或者ArrayList<Table>,根據上一篇《JAVA泛型——基本使用》的結論,這實際也是不可以的。可以用下列代碼驗證:
Auction auctionThree = new Auction("auctionThree");
itemList.add(auctionThree);
Auction tableThree = new Auction("tableThree");
itemList.add(tableThree);
Auction serviceThree = new Auction("serviceThree");
itemList.add(serviceThree);
因此這也說明了剛開始看到的addDecorate2中的語句itemList.add(t);為什么會編譯報錯。雖然如此,我們依然希望能將子類型添加到父類型聲明的泛型中,這該怎么辦?Java當然也考慮到這個問題,請看下一篇《JAVA泛型——逆變》。
總結
如果從泛型中獲取子類,應該使用協變。
如需轉載,請標明本文作者及出處——作者:jiaan.gja,本文原創首發:博客園,原文鏈接:http://www.cnblogs.com/jiaan-geng/p/4916347.html

