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

原文出處: 併發編程網java

常常發現有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>。ide

    你不能保證讀取到Double,由於foo3可能指向的是List<Integer>。函數

  2. 寫入操做過以上給定的賦值語句,你能把一個什麼類型的元素合法地插入到foo3中呢?

    你不能插入一個Integer元素,由於foo3可能指向List<Double>。this

    你不能插入一個Double元素,由於foo3可能指向List<Integer>。spa

    你不能插入一個Number元素,由於foo3可能指向List<Integer>。.net

    你不能往List<? extends T>中插入任何類型的對象,由於你不能保證列表實際指向的類型是什麼,你並不能保證列表中實際存儲什麼類型的對象。惟一能夠保證的是,你能夠從中讀取到T或者T的子類。code

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);  
相關文章
相關標籤/搜索