java泛型中 有什么區別?


List<T>和List<?>的區別,這問的有點含糊。我想題主是想問類型參數“<T>”無界通配符“<?>”的區別吧?

討論“<T>"和"<?>",首先要區分開兩種不同的場景:

  1. 第一,聲明一個泛型類或泛型方法。
  2. 第二,使用泛型類或泛型方法。

類型參數“<T>”主要用於第一種,聲明泛型類或泛型方法。 無界通配符“<?>”主要用於第二種,使用泛型類或泛型方法。

1. <T>聲明泛型類的類型參數

List<T>最應該出現的地方,應該是定義一個泛型List容器。但List是庫里自帶的容器,看看ArrayList的源碼頭一行:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.o.Serializable {
    ... ...
}

ArrayList<E>中的“E”也是類型參數。只是表示容器中元素Element的時候,習慣用“E”。換一個簡單的例子,我們自己定義一個新泛型容器叫Box<T>。

class Box<T>{
    private T item1;
    private T item2;
}

為什么這里要用類型參數?因為這是一種”約束“,為了保證Box里的item1, item2都是同一個類型T。Box<String>,代表兩個item都是String。Box<Integer>里兩個item都是Integer。

List容器庫里都幫我們寫好了,所以我們是不會去定義List<T>的。

那什么時候會出現List<T>?有幾種情況,

要么是作為泛型類的成員字段或成員方法的參數間接出現。還是剛才Box<T>的例子,

class Box<T>{
    private List<T> item;
    public List<T> get(){return item;}
    public void set(List<T> t){item=t;}
}

現在Box類里有三個地方出現了List<T>:

  1. 成員字段item的類型
  2. get( )方法的返回值
  3. set( )方法的參數

這里寫成List<T>為了表示和Box<T>類型參數保持一致。

2. <T>聲明泛型方法

另外一種會出現List<T>的地方是泛型方法。比如Function類的reduce是個靜態泛型方法,負責對列表里的所有元素求和。這里的List<T>出現在參數,函數返回值和函數內部,也是為了保持泛型類型的一致性。

class Fuction{
    public static <T> List<T> reduce(List<T> list){
        //...do something     }
}

 

3. 聲明泛型類不能用無界通配符<?>

反觀List<?>,首先要明確通配符不能拿來聲明泛型。像下面這樣用通配符"?"來表示類型參數的約束是肯定不行的。

//Error Example class Box<?>{
    private ? item1;
    private ? item2;
}

 

通配符是拿來使用定義好的泛型的。比如用<?>聲明List容器的變量類型,然后用一個實例對象給它賦值的時候就比較靈活。

List<?> list = new ArrayList<String>();

 

4. <?>的各種坑

List<?>這個寫法非常坑。因為,這時候通配符會捕獲具體的String類型,但編譯器不叫它String,而是起個臨時的代號,比如”CAP#1“。所以以后再也不能往list里存任何元素,包括String。唯一能存的就是null。

List<?> list = new ArrayList<String>();

list.add("hello");    //ERROR list.add(111);    //ERROR 
//argument mismatch; String cannot be converted to CAP#1 //argument mismatch; int cannot be converted to CAP#1

 

另外如果拿List<?>做參數,也會有奇妙的事情發生。還是剛才Box<T>的例子,有get()和set()兩個方法,一個存,一個取。

class Box<T>{
    private List<T> item;
    public List<T> get(){return item;}
    public void set(List<T> t){item=t;}
    //把item取出來,再放回去     public void getSet(Box<?> box){box.set(box.get());}    //ERROR }

 

新的getSet()方法,只是把item先用get()方法讀出來,然后再用set()方法存回去。按理說不可能有問題。實際運行卻會報錯。

error: incompatible types: Object cannot be converted to CAP#1

原因和前面一樣,通配符box<?>.set()的參數類型被編譯器捕獲,命名為CAP#1,和box<?>.get()返回的Object對象無法匹配。

解決方法,是要給getSet()方法寫一個輔助函數,具體原理可以去查《Java核心技術-卷1》,泛型這章,或者《Java編程思想》。都有講。

class Box<T>{
    private List<T> item;
    public List<T> get(){return item;}
    public void set(List<T> t){item=t;}
    //helper()函數輔助getSet()方法存取元素     public void getSet(Box<?> box){helper(box);}
    public <V> void helper(Box<V> box){box.set(box.get());}
}

 

5. 有界通配符<? extends XXX>,<? super XXX>

實際更常用的是<? extends XXX>或者<? super XXX>兩種,帶有上下界的通配符。關於這兩種情況可以參考另一個回答:

Java 泛型 <? super T> 中 super 怎么 理解?與 extends 有何不同? - 胖胖的回答


免責聲明!

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



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