1. 程式人生 > 實用技巧 >Java中型別引數“<T>”和無界萬用字元“<?>的區別

Java中型別引數“<T>”和無界萬用字元“<?>的區別

討論" "和"<?>",首先要區分開兩種不同的場景
1.第一,宣告一個泛型類或泛型方法。
2.第二,使用泛型類或泛型方法。
型別引數“"主要用於第一種,宣告泛型類或泛型方法。無界萬用字元 "<?>"主要用於第二種,使用泛型類或泛型方法。

宣告泛型類的型別引數

List最應該出現的地方,應該是定義一個泛型List容器。但List是庫裡自帶的容器,看看ArrayList的原始碼頭一行:

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

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

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

為什麼這裡要用型別引數?因為這是一種”約束“,為了保證Box裡的item1, item2都是同一個型別T。Box,代表兩個item都是String。Box,代表兩個item都是Integer。

List容器庫裡都幫我們寫好了,所以我們是不會去定義List的。那什麼時候會出現List呢?作為泛型類的成員欄位或成員方法的引數間接出現,還是剛才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:

  1. 成員欄位item的型別
  2. get( )方法的返回值
  3. set( )方法的引數

這裡寫成List為了表示和Box型別引數保持一致。

宣告泛型方法

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

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

宣告泛型類不能用無界萬用字元<?>

反觀List<?>,首先要明確萬用字元不能拿來宣告泛型。像下面這樣用萬用字元"?"來表示型別引數的約束是肯定不行的。

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

萬用字元是拿來使用定義好的泛型的。比如用<?>宣告List容器的變數型別,然後用一個例項物件給它賦值的時候就比較靈活。

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

<?>的各種坑

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

List<?> list = new ArrayList<String>();
list . add("he1lo");
//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的例子,有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){heIper(box);}
public <V> void helper(Box<V> box)box.setbox.gt0);}
}

有界萬用字元<? extends XXX>和<? super XXX>

和是Java泛型中的“萬用字元(Wildcards)”和“邊界(Bounds)”的概念。其中,:是指 “上界萬用字元(Upper Bounds Wildcards)”,:是指 “下界萬用字元(Lower Bounds Wildcards)”。 開發人員在使用泛型的時候,很容易根據自己的直覺而犯一些錯誤。比如一個方法如果接收 List