更新于 2020.9.1
在 Java 泛型中存在通配符的概念:
<? extends T>:上界通配符(Upper Bounds Wildcards)
<? super T>:下界通配符(Lower Bounds Wildcards)
通配符存在的意義
有類就會有繼承,在開發(fā)中我們經(jīng)常接觸泛型,肯定希望在使用泛型的時候可以兼容其子類,但是在使用集合或者其他類似的情景時會出現(xiàn)問題。下面用集合例子模擬真實的需求:
A a = new B();
ArrayList<A> list= new ArrayList<B>(); // 報錯
根據(jù)上面的代碼,假設(shè) B 繼承 A,第一行代碼是合法的,但是第二行代碼編譯器會直接紅線報錯,ArrayList<B> 無法賦值給 ArrayList<A>。這是因為編譯器在確定 ArrayList 元素類型的時候不會考慮 A 和 B 的繼承關(guān)系,編譯器無法承認(rèn)第二行代碼是合法的。為了解決這個問題引入了通配符的概念。
Ps:該例子中用的是 ArrayList 做例子,但并不是只針對集合,是針對類似集合出現(xiàn)的問題的情景。

根據(jù)上面的類的繼承關(guān)系圖,我們用 C 來觀察,假設(shè)有下面兩個 List:
List<? extends C> list1; // list1 的元素的類型只能是 C 和 C 的子類。
List<? super C> list2; // list2 的元素的類型只能是 C 和 C 的父類。
list1 和 list2 的類型區(qū)間:
ArrayList<B> b = new ArrayList<>();
ArrayList<C> c = new ArrayList<>();
ArrayList<D> d = new ArrayList<>();
// <? extends C>
list1 = b; // 報錯
list1 = c;
list1 = d;
// <? super C>
list2 = b;
list2 = c;
list2 = d; // 報錯
注意: 編譯器針對 <? extends C> 和 <? super C> 只關(guān)心與 C 有關(guān)的類繼承關(guān)系,即只關(guān)心 C 的所有父類、 C 的所有子類和 C 本身的繼承關(guān)系,即使 A 或者 B 存在其他子類編譯器都不關(guān)心。
下面先認(rèn)識 <? extends C> 和 <? super C> 的區(qū)別:
-
<? extends C>
-
add(): 不可以加入任何元素(除了 null 因為 null 可以為任何類型),編譯器會紅線報錯,如:
/** * Required type: capture of ? extends C * Provided: B */ // list1.add(new B()); // list1.add(new C()); // list1.add(new D()); list1.add(null);
-
-
get():返回的值的類型默認(rèn)是 C ,如:
C c = list1.get(0); /** * 編譯器不報錯,但如果 list1 第0個元素是 E 或者 F 的話, * 運行時存在強制類型轉(zhuǎn)換失敗程序崩潰的風(fēng)險 * Exception in thread "main" java.lang.ClassCastException: * E cannot be cast to D */ D d1 = (D) list.get(0);
-
<? super C>
-
add(): 只能添加類型為 C 和其子類類型的元素,如:
/** * Required type: capture of ? super C * Provided: B */ // list2.add(new B()); // 報錯 list2.add(new C());
list2.add(new D());
list2.add(new E());
list2.add(new F()); -
get():返回的值的類型默認(rèn)是 Object,如:
Object object = list2.get(0);
-
簡單來說就是 <? extends C> 上界為 C 類型范圍粗略理解為 [ C , +∞ ),不允許添加除 null 的元素,get() 的元素類型默認(rèn)是 C ,當(dāng)然在知道集合元素類型的情況下通過強轉(zhuǎn)程序是允許的;
<? super C> 下界為 C 類型范圍粗略理解為 ( -∞ , C ],允許添加 C 以及 C 的子類類型的元素,獲取的元素類型是 Object
從編譯器的角度理解上面的區(qū)別:
首先理解車的例子:單車是車的子類,單車有車的特性可以代表車,但是車不能代表單車。即類型向上兼容向下不兼容。
- <? extends C>
- add() : 編譯器只知道元素類型是 C 或 C 的子類,所以有可能是 C D E F 其中一個類型,為保證類型安全不能添加除了 null 以外的任何元素,即使是 C 本身也不行(車不能代表單車).
- get() : 既然編譯器不知道此時集合中的元素是 C D E F 的哪一個,返回類型只能是他們共同父類 C 類型.
- <? super C>
- add() : 編譯器只知道元素類型是 C 或者 C 的父類,所以有可能是 A B C 其中一個類型。編譯器知道下界是 C ,根據(jù)類型向上兼容所以可以添加的元素是 C 以及 C 的子類(單車可以代表車).
- get() : 既然編譯器不確定集合類型是 A B C 的哪一種,返回類型只能是他們的共同父類 Object .
特點:<? extends C> 的 add() 被限制,<? super C> 的 get() 被限制
PECS 原則
<? extends C> 適合大量做獲取操作的情景,<? super C> 適合大量做添加操作的情景。