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

來源:互聯網
上載者:User

標籤:

  泛型是在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());    }}

 



 

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.