泛型除了KTV,還有一個讓人比較疑惑的玩意,而且它就是用來表達疑惑的:? 雖然通過泛型已經達到我們想要的效果了,例如: List<String> list = new ArrayList<String>(); 這樣就可以放心地存取String類型的數據。 但是(抱歉,凡事總有個但是),應用的場景總是 ...
泛型除了KTV,還有一個讓人比較疑惑的玩意,而且它就是用來表達疑惑的:?
雖然通過泛型已經達到我們想要的效果了,例如:
List<String> list = new ArrayList<String>();
這樣就可以放心地存取String類型的數據。
但是(抱歉,凡事總有個但是),應用的場景總是在不斷增加的。某一天:
老闆:咱們之前給客戶開發的功能中有個地方要改一改。
神牛:哪裡要改呢?
老闆:以前你寫的代碼List<Cat> list = new ArrayList<Cat>();只能列出Java寵物店托管的貓咪,但是現在Java寵物店已經擴大了經營範圍,希望列出他們保管所有的寵物,只要是寵物就行......
神牛:這個easy!
老闆:真的嗎?
於是,神牛一通操作,代碼就改成了這樣:
class Cat extends Pets {}; class Dog extends Pets {}; public static void main(String[] args) { List<? extends Pets> list = new ArrayList<Pets>(); Pets pets = list.get(0); Cat cat = (Cat)list.get(1); Dog dog = (Dog)list.get(2); }
然鵝,過了一段時間,Java寵物店由於經營不善,已經將之前的寵物轉賣、送人了一部分,現在就剩一些貓科動物,所以現在的寵物籠子需要重新分配,只要是貓科動物就要往裡放。以前寫的代碼
List<? extends Pets> list = new ArrayList<Pets>();
就滿足不了給寵物分配籠子的需求了(先想一想為啥不行了?)
神牛繼續把鍵盤一頓猛敲,代碼又改成了這樣:
class Felidae {}; class Cat extends Felidae {}; public static void main(String[] args) { List<? super Felidae> list = new ArrayList<Felidae>(); Cat cat = new Cat(); list.add(cat); }
這樣一改,以前的功能又不能用了(為啥不能列出保管的寵物了?)
從以上需求場景可以看到:
1、對於不確定或者不關心實際要操作的類型,可以使用無界通配符(尖括弧里一個問號,即 <?>),表示可以持有任何類型;
2、<? extends T>稱之為「上界通配符」,表示只允許T及T的子類調用,例如只允許寵物類Pets的子類Cat和Dog調用;
3、<? super T>剛好相反,稱之為「下界通配符」,表示只允許T及T的父類調用,例如只允許Cat的父類Felidae調用;
4、由於上界通配符<? extends T>中只知道T這個父類,而不知道具體的子類(所以用?代替),因此它無法實現向列表中加入新元素的功能,也就是做不到list.add()(這就是為什麼滿足不了給寵物分配籠子的需求);
5、而由於下界通配符<? super T>中只知道T這個子類,而不知道具體的父類(所以用?代替),因此它無法實現從列表中獲取元素的功能,也就是做不到list.get()(這也是為什麼滿足不了列出保管的寵物)。
剛纔說了那麼多,稍稍有點繞。總結一下:
由於<? extends T>的只能取,不能存;而<? super T>得只能存,不能取,因此在架構設計中就有一個推薦的實踐經驗:
1、生產者producer一般用<? extends T>
2、消費者consumer一般用<? super T>
泛型講到這裡,如果能夠全部明白,就可以真正暢快地去KTV嗨了。而泛型其他的知識點,像什麼無界通配符、泛型參數一致性、多重限定、基類劫持介面、自限定類型、迴圈泛型等亂七八糟的可以統統不去管了,因為很多工程師一輩子的職業生涯中幾乎都碰不到它們,除非點背到極點。還是最開始的那幾個建議:
1、不鑽牛角尖,有問題見招拆招
2、解決主要巨集觀上、業務上的問題,暫時忽略次要的技術上的、細節上的問題
3、抓大放小,用好80/20原則