? 通配符類型
- <? extends T> 表示類型的上界,? 表示參數化類型的可能是 T 或是 T 的子類
- <? super T> 表示類型下界,? 表示參數化類型是此 T 類型的超類型 (父類型),直至 Object
上界 <? extends T>
不能往裏存,只能往外取
- 比如,我們現在定義:
List<? extends T>
首先你很容易誤解它爲繼承於T的所有類的集合,你可能認爲,你定義的這個List可以用來put任何T的子類,那麼我們看下面的代碼:- List<? extneds Father> 表示 “具有任何從 Son 繼承類型的列表”,編譯器無法確定 List 所持有的類型,所以無法安全的向其中添加對象。可以添加 null,因爲 null 可以表示任何類型。所以 List 的 add() 不能添加任何有意義的元素,但是可以接受現有的子類型 List 爲賦值。
- 你也許試圖這樣做:
- 即使你指明瞭爲 Son 類型,也不能用 add() 添加一個 Son 對象。
- List 中爲什麼不能加入 Father 類和 Father 類的子類,分析如下:
- List<? extneds Father> 表示上限是 Father,下面這樣的賦值是合法的,並且如果讓 List<? extends Father> 支持 add() 的話
List<? extends Father> list1 = new ArrayList<Father>(); List<? extends Father> list2 = new ArrayList<Son>(); List<? extends Father> list3 = new ArrayList<LeiFeng>(); // list1 可以 add Father 和所有 Father 的子類 // list2 可以 add Son 和所有 Son 的子類 // list3 可以 add LeiFeng 和所有 LeiFeng 的子類 // 下面代碼還是會編譯不通過: list1.add(new Father()); //error list1.add(new Son()); //error
- 原因是編譯器只知道容器內是Father或者它的派生類,但具體是什麼類型不知道。可能是Father?可能是Son?也可能是LeiFeng?編譯器在看到後面用 Father 賦值以後,集合裏並沒有限定參數類型是“Father“。而是標上一個佔位符:CAP#1,來表示捕獲一個 Father 或 Father 的子類,具體是什麼類不知道,代號CAP#1。然後無論是想往裏插入 Son 或者 LeiFeng 或者Father 編譯器都不知道能不能和這個 CAP#1 匹配,所以就都不允許。
- 所以通配符 <?> 和類型參數的區別就在於,對編譯器來說所有的 T 都代表同一種類型。比如下面這個泛型方法裏,三個T都指代同一個類型,要麼都是 String,要麼都是 Integer。
public <T> List<T> fill(T... t);
- 但通配符 <?> 沒有這種約束,
List
<?> 單純的就表示:集合裏放了一個東西,是什麼我不知道。- 所以這裏的錯誤就在這裏,List<? extneds Father> 裏什麼都放不進去。
- List<? extends Father> list 不能進行 add(),但是這種形式還是很有用的,雖然不能使用 add(),但是可以在初始化的時候指定不同的類型。比如:
List<? extends Father> list1 = getFatherList(); // getFatherList方法會返回一個Father的子類的list
- 另外,由於我們已經保證了 List 中保存的是 Father 類或者他的某一個子類,所以,可以用get方法直接獲得值:
List<? extends Father> list1 = new ArrayList<>(); Father father = list1.get(0); // 讀取出來的東西只能存放在Father或它的基類裏。 Object object = list1.get(0); // 讀取出來的東西只能存放在Father或它的基類裏。 Human human = list1.get(0); // 讀取出來的東西只能存放在Father或它的基類裏。 Son son = (Son)list1.get(0);
下界 <? super T>
// super 只能添加 Father 和 Father 的子類,不能添加 Father 的父類,讀取出來的東西只能存放在Object類裏 List<? super Father> list = new ArrayList<>(); list.add(new Father()); list.add(new Human()); // compile error list.add(new Son()); Father person1 = list.get(0); // compile error Son son = list.get(0); // compile error Object object1 = list.get(0);
- 因爲下界規定了元素的最小粒度的下限,實際上是放鬆了容器元素的類型控制。既然元素是 Father 的基類,那往裏存粒度比 Father 小的都可以。出於對類型安全的考慮,我們可以加入 Father 對象或者其任何子類 (如Son)對象,但由於編譯器並不知道List 的內容究竟是 Father 的哪個超類,因此不允許加入特定的任何超類 (如Human)。而當我們讀取的時候,編譯器在不知道是什麼類型的情況下只能返回 Object 對象,因爲 Object 是任何 Java 類的最終祖先類。但這樣的話,元素的類型信息就全部丟失了。
PECS原則
最後看一下什麼是PECS(Producer Extends Consumer Super)原則,已經很好理解了:
- 頻繁往外讀取內容的,適合用上界 Extends。
- 經常往裏插入的,適合用下界 Super。
總結
- extends 可用於返回類型限定,不能用於參數類型限定(換句話說:? extends xxx 只能用於方法返回類型限定,JDK 能夠確定此類的最小繼承邊界爲xxx,只要是這個類的父類都能接收,但是傳入參數無法確定具體類型,只能接受 null 的傳入)。
- super 可用於參數類型限定,不能用於返回類型限定(換句話說:? supper xxx 只能用於方法傳參,因爲 JDK 能夠確定傳入爲xxx的子類,返回只能用 Object 類接收)。
- ? 既不能用於方法參數傳入,也不能用於方法返回。
- 注意:
- 1、List<Object> o = new ArrayList<Long>(); // 會報錯
- 不同於數組 Object[] o,Long[] o,因爲 List<Type1> 與 List<Type2> 不互爲子類型或超類型
- 2、泛型方法的使用
- 定義泛型方法語法格式如下:
- 這就是所謂的泛型方法,意思是這個方法所自己定義的泛型,在調用時,根據參數類型確定它的類型。
- 調用泛型方法語法格式如下: