原文出處: 並發編程網
經常發現有List<? super T>、Set<? extends T>的聲明,是什么意思呢?<? super T>表示包括T在內的任何T的父類,<? extends T>表示包括T在內的任何T的子類,下面我們詳細分析一下兩種通配符具體的區別。
extends
List<? extends Number> foo3的通配符聲明,意味着以下的賦值是合法的:
01 |
// Number "extends" Number (in this context) |
02 |
03 |
List<? extends Number> foo3 = new ArrayList<? extends Number>(); |
04 |
05 |
// Integer extends Number |
06 |
07 |
List<? extends Number> foo3 = new ArrayList<? extends Integer>(); |
08 |
09 |
// Double extends Number |
10 |
11 |
List<? extends Number> foo3 = new ArrayList<? extends Double>(); |
- 讀取操作通過以上給定的賦值語句,你一定能從foo3列表中讀取到的元素的類型是什么呢?你可以讀取到Number,因為以上的列表要么包含Number元素,要么包含Number的類元素。
你不能保證讀取到Integer,因為foo3可能指向的是List<Double>。
你不能保證讀取到Double,因為foo3可能指向的是List<Integer>。
- 寫入操作過以上給定的賦值語句,你能把一個什么類型的元素合法地插入到foo3中呢?
你不能插入一個Integer元素,因為foo3可能指向List<Double>。
你不能插入一個Double元素,因為foo3可能指向List<Integer>。
你不能插入一個Number元素,因為foo3可能指向List<Integer>。
你不能往List<? extends T>中插入任何類型的對象,因為你不能保證列表實際指向的類型是什么,你並不能保證列表中實際存儲什么類型的對象。唯一可以保證的是,你可以從中讀取到T或者T的子類。
super
現在考慮一下List<? super T>。
List<? super Integer> foo3的通配符聲明,意味着以下賦值是合法的:
01 |
// Integer is a "superclass" of Integer (in this context) |
02 |
03 |
List<? super Integer> foo3 = new ArrayList<Integer>(); |
04 |
05 |
// Number is a superclass of Integer |
06 |
07 |
List<? super Integer> foo3 = new ArrayList<Number>(); |
08 |
09 |
// Object is a superclass of Integer |
10 |
11 |
List<? super Integer> foo3 = new ArrayList<Object>(); |
- 讀取操作通過以上給定的賦值語句,你一定能從foo3列表中讀取到的元素的類型是什么呢?你不能保證讀取到Integer,因為foo3可能指向List<Number>或者List<Object>。
你不能保證讀取到Number,因為foo3可能指向List<Object>。
唯一可以保證的是,你可以讀取到Object或者Object子類的對象(你並不知道具體的子類是什么)。
- 寫入操作通過以上給定的賦值語句,你能把一個什么類型的元素合法地插入到foo3中呢?你可以插入Integer對象,因為上述聲明的列表都支持Integer。
你可以插入Integer的子類的對象,因為Integer的子類同時也是Integer,原因同上。
你不能插入Double對象,因為foo3可能指向ArrayList<Integer>。
你不能插入Number對象,因為foo3可能指向ArrayList<Integer>。
你不能插入Object對象,因為foo3可能指向ArrayList<Integer>。
PECS
請記住PECS原則:生產者(Producer)使用extends,消費者(Consumer)使用super。
- 生產者使用extends
如果你需要一個列表提供T類型的元素(即你想從列表中讀取T類型的元素),你需要把這個列表聲明成<? extends T>,比如List<? extends Integer>,因此你不能往該列表中添加任何元素。
- 消費者使用super
如果需要一個列表使用T類型的元素(即你想把T類型的元素加入到列表中),你需要把這個列表聲明成<? super T>,比如List<? super Integer>,因此你不能保證從中讀取到的元素的類型。
- 即是生產者,也是消費者
如果一個列表即要生產,又要消費,你不能使用泛型通配符聲明列表,比如List<Integer>。
例子
請參考java.util.Collections里的copy方法(JDK1.7):
引用例子:
泛型中使用通配符有兩種形式:子類型限定<? extends xxx>和超類型限定<? super xxx>。
(1)子類型限定
下面的代碼定義了一個Pair<T>類,以及Employee,Manager和President類。
- public class Pair<T> {
- private T first;
- private T second;
- public Pair(T first, T second) {
- this.first = first;
- this.second = second;
- }
- public T getFirst() {
- return first;
- }
- public T getSecond() {
- return second;
- }
- public void setFirst(T newValue) {
- first = newValue;
- }
- public void setSecond(T newValue) {
- second = newValue;
- }
- }
- class Employee {
- private String name;
- private double salary;
- public Employee(String n, double s) {
- name = n;
- salary = s;
- }
- public String getName() {
- return name;
- }
- public double getSalary() {
- return salary;
- }
- }
- class Manager extends Employee {
- public Manager(String n, double s) {
- super(n, s);
- }
- }
- <pre name="code" class="java">
- class President extends Manager {
- public President(String n, double s) {
- super(n, s);
- }
- }
現在要定義一個函數可以打印Pair<Employee>
- public static void printEmployeeBoddies(Pair<Employee> pair) {
- System.out.println(pair.getFirst().getName() + ":" + pair.getSecond().getName());
- }
可是有一個問題是這個函數輸入參數只能傳遞類型Pair<Employee>,而不能傳遞Pair<Manager>和Pair<President>。例如下面的代碼會產生編譯錯誤
- Manager mgr1 = new Manager("Jack", 10000.99);
- Manager mgr2 = new Manager("Tom", 10001.01);
- Pair<Manager> managerPair = new Pair<Manager>(mgr1, mgr2);
- PairAlg.printEmployeeBoddies(managerPair);
之所以會產生編譯錯誤,是因為Pair<Employee>和Pair<Manager>實際上是兩種類型。
由上圖可以看出,類型Pair<Manager>是類型Pair<? extends Employee>的子類型,所以為了解決這個問題可以把函數定義改成
public static void printEmployeeBoddies(Pair<? extends Employee> pair)
但是使用通配符會不會導致通過Pair<? extends Employee>的引用破壞Pair<Manager>對象呢?例如:
Pair<? extends Employee> employeePair = managePair;employeePair.setFirst(new Employee("Tony", 100));
不用擔心,編譯器會產生一個編譯錯誤。Pair<? extends Employee>參數替換后,我們得到如下代碼
? extends Employee getFirst()
void setFirst(? extends Employee)
對於get方法,沒問題,因為編譯器知道可以把返回對象轉換為一個Employee類型。但是對於set方法,編譯器無法知道具體的類型,所以會拒絕這個調用。
(2)超類型限定
超類型限定和子類型限定相反,可以給方法提供參數,但是不能使用返回值。? super Manager這個類型限定為Manager的所有超類。
Pair<? super Manager>參數替換后,得到如下方法
? super Manager getFirst()
void setFirst(? super Manager)
編譯器可以用Manager的超類型,例如Employee,Object來調用setFirst方法,但是無法調用getFirst,因為不能把Manager的超類引用轉換為Manager引用。
超類型限定的存在是為了解決下面一類的問題。例如要寫一個函數從Manager[]中找出工資最高和最低的兩個,放在一個Pair中返回。
- public static void minMaxSal(Manager[] mgrs, Pair<? super Manager> pair) {
- if (mgrs == null || mgrs.length == 0) {
- return;
- }
- pair.setFirst(mgrs[0]);
- pair.setSecond(mgrs[0]);
- //TODO
- }
如此就可以這樣調用
- Pair<? super Manager> pair = new Pair<Employee>(null, null);
- minMaxSal(new Manager[] {mgr1, mgr2}, pair);