Java泛型的協變
在上篇《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); } }
因為聲明了decoratorA的類型是Decorator<Auction>,那麽此時實例decoratorA的doDecorate2的參數能夠覺得是以下這樣:
List<? extends Auction> itemList
而listB的類型是ArrayList<Table>,listC的類型是ArrayList<Service>,也就是說協變同意:
List<? extends Auction> itemList = new這說明itemList中既能夠存儲Auction。也能夠存儲Table和Service。ArrayList<Auction>(); List<? extends Auction> itemList = new ArrayList<Table>(); List<? extends Auction> itemList = new ArrayList<Service>();
我們做個如果,當你想從itemList中獲取Table的時候,Java編譯器懷疑這實際可能是個Service。因此會有編譯錯誤。如果想從itemList中獲取Auction是沒有問題的,由於不管是Table還是Service它們都能夠被自己主動轉換為父類Auction。這些猜測,能夠通過代碼清單3驗證。
代碼清單3
List<?相同的道理,當你想要向itemList中加入Table時,編譯器懷疑itemList實際是個ArrayList<Service>,因此是不同意的。在獲取Auction時是沒有問題的,那麽加入Auction會不會也能夠?因為itemList可能是ArrayList<Service>或者ArrayList<Table>,依據上一篇《Java泛型的基本使用》的結論,這實際也是不能夠的。能夠用下列代碼驗證: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);
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泛型的逆變》。
總結
假設從泛型中獲取子類,應該使用協變。
Java泛型的協變