泛型中? super T和? extends T的區別


原文出處: 並發編程網

經常發現有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>();
  1. 讀取操作通過以上給定的賦值語句,你一定能從foo3列表中讀取到的元素的類型是什么呢?你可以讀取到Number,因為以上的列表要么包含Number元素,要么包含Number的類元素。

    你不能保證讀取到Integer,因為foo3可能指向的是List<Double>。

    你不能保證讀取到Double,因為foo3可能指向的是List<Integer>。

  2. 寫入操作過以上給定的賦值語句,你能把一個什么類型的元素合法地插入到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>();
  1. 讀取操作通過以上給定的賦值語句,你一定能從foo3列表中讀取到的元素的類型是什么呢?你不能保證讀取到Integer,因為foo3可能指向List<Number>或者List<Object>。

    你不能保證讀取到Number,因為foo3可能指向List<Object>。

    唯一可以保證的是,你可以讀取到Object或者Object子類的對象(你並不知道具體的子類是什么)。

  2. 寫入操作通過以上給定的賦值語句,你能把一個什么類型的元素合法地插入到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類。

 
  1. public class Pair<T> {  
  2.     private T first;  
  3.     private T second;  
  4.   
  5.     public Pair(T first, T second) {  
  6.         this.first = first;  
  7.         this.second = second;  
  8.     }  
  9.   
  10.     public T getFirst() {  
  11.         return first;  
  12.     }  
  13.   
  14.     public T getSecond() {  
  15.         return second;  
  16.     }  
  17.   
  18.     public void setFirst(T newValue) {  
  19.         first = newValue;  
  20.     }  
  21.   
  22.     public void setSecond(T newValue) {  
  23.         second = newValue;  
  24.     }  
  25. }  
  26.   
  27. class Employee {  
  28.     private String name;  
  29.     private double salary;  
  30.       
  31.     public Employee(String n, double s) {  
  32.         name = n;  
  33.         salary = s;  
  34.     }  
  35.       
  36.     public String getName() {  
  37.         return name;  
  38.     }  
  39.   
  40.     public double getSalary() {  
  41.         return salary;  
  42.     }  
  43. }  
  44.   
  45. class Manager extends Employee {  
  46.     public Manager(String n, double s) {  
  47.         super(n, s);  
  48.     }  
  49. }  
  50. <pre name="code" class="java">  
  51. class President extends Manager {  
  52.     public President(String n, double s) {  
  53.         super(n, s);  
  54.     }  
  55. }  

 

 
        
 
        

現在要定義一個函數可以打印Pair<Employee>


 在CODE上查看代碼片派生到我的代碼片
  1. public static void printEmployeeBoddies(Pair<Employee> pair) {  
  2.     System.out.println(pair.getFirst().getName() + ":" + pair.getSecond().getName());  
  3. }  

可是有一個問題是這個函數輸入參數只能傳遞類型Pair<Employee>,而不能傳遞Pair<Manager>和Pair<President>。例如下面的代碼會產生編譯錯誤

 
  1. Manager mgr1 = new Manager("Jack", 10000.99);  
  2. Manager mgr2 = new Manager("Tom", 10001.01);  
  3. Pair<Manager> managerPair = new Pair<Manager>(mgr1, mgr2);  
  4. 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中返回。

 
  1. public static void minMaxSal(Manager[] mgrs, Pair<? super Manager> pair) {  
  2.     if (mgrs == null || mgrs.length == 0) {  
  3.         return;  
  4.     }  
  5.       
  6.     pair.setFirst(mgrs[0]);  
  7.     pair.setSecond(mgrs[0]);  
  8.     //TODO  
  9. }  

如此就可以這樣調用


 在CODE上查看代碼片派生到我的代碼片
    1. Pair<? super Manager> pair = new Pair<Employee>(null, null);  
    2. minMaxSal(new Manager[] {mgr1, mgr2}, pair);  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM