淺談Java泛型中的extends和super關鍵字


  泛型是在Java 1.5中被加入了,這里不討論泛型的細節問題,這個在Thinking in Java第四版中講的非常清楚,這里要講的是super和extends關鍵字,以及在使用這兩個關鍵字的時候為什么會不同的限制。 
  首先,我們定義兩個類,A和B,並且假設B繼承自A。

package com.wms.test;

import java.util.ArrayList;
import java.util.List;

public class Generic {  
    public static void main(String[] args){  
          List<? extends A> list1 = new ArrayList<A>(); 
//          list1.add(new A()); //錯誤,編譯器無法確定List所持有的類型,所以無法安全的向其中添加對象
          A a = list1.get(0);
          List<? extends A> list2 = new ArrayList<B>();  
          List<? super B> list3 = new ArrayList<B>(); 
          list3.add(new B());
          //想要正確,必須向下轉型,但是向下轉型是不安全的,非常容易出錯
//          B b = list3.get(0); //編譯器無法確定get返回的對象類型是B,還是B的父類或 Object.
          List<? super B> list4 = new ArrayList<A>();  
       } 
}  

class A{}
class B extends A{}

  從上面這段創建List的代碼我們就更加容易理解super和extends關鍵字的含義了。首先要說明的一點是,Java強制在創建對象的時候必須給類型參數制定具體的類型,不能使用通配符,也就是說new ArrayList<? extends A>(),new ArrayList<?>()這種形式的初始化語句是不允許的。 

  從上面main函數的第一行和第二行,我們可以理解extends的含義,在創建ArrayList的時候,我們可以指定A或者B作為具體的類型。也就是,如果<? extends X>,那么在創建實例的時候,我們就可以用X或者擴展自X的類為泛型參數來作為具體的類型,也可以理解為給"?"號指定具體類型,這就是extends 的含義。 

  同樣的,第三行和第四行就說明,如果<? super X>,那么在創建實例的時候,我們可以指定X或者X的任何的超類來作為泛型參數的具體類型。 

  當我們使用List<? extends X>這種形式的時候,調用List的add方法會導致編譯失敗,因為我們在創建具體實例的時候,可能是使用了X也可能使用了X的子類,而這個信息編譯器是沒有辦法知道的,同時,對於ArrayList<T>來說,只能放一種類型的對象。這就是問題的本質。而對於get方法來說,由於我們是通過X或者X的子類來創建實例的,而用超類來引用子類在Java中是合法的,所以,通過get方法能夠拿到一個X類型的引用,當然這個引用可以指向X也可以指向X的任何子類。 

    而當我們使用List<? super X>這種形式的時候,調用List的get方法會失敗。因為我們在創建實例的時候,可能用了X也可能是X的某一個超類,那么當調用get的時候,編譯器是無法准確知曉的。而調用add方法正好相反,由於我們使用X或者X的超類來創建的實例,那么向這個List中加入X或者X的子類肯定是沒有問題的, 因為超類的引用是可以指向子類的。 

    最后還有一點,這兩個關鍵字的出現都是因為Java中的泛型沒有協變特性的導致的。

小結(此處還是沒太明白,有待解決)

extends 可用於的返回類型限定,不能用於參數類型限定。 super 可用於參數類型限定,不能用於返回類型限定。 >帶有super超類型限定的通配符可以向泛型對象中寫入,帶有extends子類型限定的通配符可以向泛型對象讀取。

什么是PECS? 

 
        

  PECS指“Producer Extends,Consumer Super”。換句話說,如果參數化類型表示一個生產者,就使用<? extends T>;如果它表示一個消費者,就使用<? super T>,可能你還不明白,不過沒關系,接着往下看好了。

下面是一個簡單的Stack的API接口:

public interface Stack<E>{
    public Stack();
    public void push(E e):
    public E pop();
    public boolean isEmpty();
}

 

假設想增加一個方法,按順序將一系列元素全部放入Stack中,你可能想到的實現方式如下:
public void pushAll(Iterable<E> iter) {
    for(E e : iter)
        push(e);
}
假設有個Stack<Number>,想要靈活的處理Integer,Long等Number的子類型的集合
Stack<Number> stack = new Stack<Number>();
Iterable<Integer> iter = null;
/*
 * 此時代碼編譯無法通過,因為對於類型Number和Integer來說,雖然后者是Number的子類,
 * 但是對於任意Number集合(如List<Number>)不是Integer集合(如List<Integer>)的超類,
 * 因為泛型是不可變的。
 */
stack.pushAll(iter); //錯誤

 

  此時代碼編譯無法通過,因為對於類型Number和Integer來說,雖然后者是Number的子類,但是對於任意Number集合(如List<Number>)不是Integer集合(如List<Integer>)的超類,因為泛型是不可變的。

  幸好java提供了一種叫有限通配符的參數化類型,pushAll參數替換為“E的某個子類型的Iterable接口”:

public void pushAll01(Iterable<? extends E> iter) {
    for(E e : iter)
        push(e);
}

 

此時再這樣調用:
/*
 * 這樣就可以正確編譯了,這里的<? extends E>就是所謂的 producer-extends。
 * 這里的Iterable就是生產者,要使用<? extends E>。
 * 因為Iterable<? extends E>可以容納任何E的子類。在執行操作時,可迭代對象的每個元素都可以當作是E來操作。
 */
stack.pushAll01(iter); //正確

  這樣就可以正確編譯了,這里的<? extends E>就是所謂的 producer-extends。這里的Iterable就是生產者,要使用<? extends E>。因為Iterable<? extends E>可以容納任何E的子類。在執行操作時,可迭代對象的每個元素都可以當作是E來操作。

  與之對應的是:假設有一個方法popAll()方法,從Stack集合中彈出每個元素,添加到指定集合中去。

public void popAll(Collection<E> c) {
    c.add(pop());
}

  假設有一個Stack<Number>和Collection<Object>對象:

Stack<Number> stack = new Stack<Number>();
Collection<Object> c = null;
/*
* 該方法要正確,必須c為Collection<Number>,和上面同理
*/
stack.popAll(c); //錯誤

  同樣上面這段代碼也無法通過,解決的辦法就是使用Collection<? super E>。這里的objects是消費者,因為是添加元素到objects集合中去。使用Collection<? super E>后,無論objects是什么類型的集合,滿足一點的是他是E的超類,所以不管這個參數化類型具體是什么類型都能將E裝進objects集合中去。

public void popAll01(Collection<? super E> c) {
    c.add(pop());
}
/*
 * 同樣上面這段代碼也無法通過,解決的辦法就是使用Collection<? super E>。
 * 這里的objects是消費者,因為是添加元素到objects集合中去。
 * 使用Collection<? super E>后,無論objects是什么類型的集合,
 * 滿足一點的是他是E的超類,所以不管這個參數化類型具體是什么類型都能將E裝進objects集合中去。
 */
stack.popAll01(c);

 

綜合代碼:
package com.wms.test;

import java.util.Collection;

public class Generic {  
    public static void main(String[] args) {

        Stack<Number> stack = new Stack<Number>();
        Iterable<Integer> iter = null;
        /*
         * 此時代碼編譯無法通過,因為對於類型Number和Integer來說,雖然后者是Number的子類,
         * 但是對於任意Number集合(如List<Number>)不是Integer集合(如List<Integer>)的超類,
         * 因為泛型是不可變的。
         */
        stack.pushAll(iter); //錯誤
        /*
         * 這樣就可以正確編譯了,這里的<? extends E>就是所謂的 producer-extends。
         * 這里的Iterable就是生產者,要使用<? extends E>。
         * 因為Iterable<? extends E>可以容納任何E的子類。在執行操作時,可迭代對象的每個元素都可以當作是E來操作。
         */
        stack.pushAll01(iter); //正確
        
        Collection<Object> c = null;
        /*
         * 該方法要正確,必須c為Collection<Number>,和上面同理
         */
        stack.popAll(c); //錯誤
        /*
         * 同樣上面這段代碼也無法通過,解決的辦法就是使用Collection<? super E>。
         * 這里的objects是消費者,因為是添加元素到objects集合中去。
         * 使用Collection<? super E>后,無論objects是什么類型的集合,
         * 滿足一點的是他是E的超類,所以不管這個參數化類型具體是什么類型都能將E裝進objects集合中去。
         */
        stack.popAll01(c);
    }
}  

class Stack<E> {
    public Stack(){
        
    }
    
    public void push(E e) {
        
    }
    
    public void pushAll(Iterable<E> iter) {
        for(E e : iter)
            push(e);
    }
    
    public void pushAll01(Iterable<? extends E> iter) {
        for(E e : iter)
            push(e);
    }
    
    public E pop() {
        return null;
    }
    
    public void popAll(Collection<E> c) {
        c.add(pop());
    }
    
    public void popAll01(Collection<? super E> c) {
        c.add(pop());
    }
}

 



 


免責聲明!

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



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