From Gossip@caterpillar

Java Gossip: 型態通配字元

假設您撰寫了一個泛型類別:
  • GenericFoo.java
public class GenericFoo<T> {
private T foo;

public void setFoo(T foo) {
this.foo = foo;
}

public T getFoo() {
return foo;
}
}

分別使用下面的程式宣告了foo1與foo2兩個參考名稱:
GenericFoo<Integer> foo1 = null;
GenericFoo<Boolean> foo2 = null;
 

那麼 foo1 就只接受GenericFoo<Integer>的實例,而foo2只接受GenericFoo<Boolean>的實例。

現在您有這麼一個需求,您希望有一個參考名稱foo可以接受所有下面的實例(List、Map或List介面以及其實介面的相關類別,在J2SE 5.0中已經針對泛型功能作了改寫,在這邊仍請將之當作介面就好,這是為了簡化說明的考量):
foo = new GenericFoo<ArrayList>();
foo = new GenericFoo<LinkedList>();
 

簡單的說,實例化型態持有者時,它必須是實作List的類別或其子類別,要宣告這麼一個參考名稱,您可以使用 '?' 通配字元,並使用"extends"關鍵字限定型態持有者的型態,例如:
GenericFoo<? extends List> foo = null;
foo = new GenericFoo<ArrayList>();
.....
foo = new GenericFoo<LinkedList>();
....
 

如果指定了不是實作List的類別或其子類別,則編譯器會回報錯誤,例如:
GenericFoo<? extends List> foo = new GenericFoo<HashMap>();
 

上面這段程式編譯器會回報以下的錯誤:
incompatible types
found : GenericFoo<java.util.HashMap>
required: GenericFoo<? extends java.util.List>
GenericFoo<? extends List> foo = new GenericFoo<HashMap>();

這樣的限定是很有用的,例如如果您想要自訂一個showFoo()方法,方法的內容實作是針對List而制定的,例如:
public void showFoo(GenericFoo foo) {
     // 針對List而制定的內容
}
 

您當然不希望任何的型態都可以傳入showFoo()方法中,您可以使用以下的方式來限定,例如:
public void showFoo(GenericFoo<? extends List> foo) {
     //
}
 

這麼一來,如果有粗心的程式設計人員傳入了您不想要的型態,例如GenericFoo<Boolean>型態的實例,則編譯器都會告訴它這是不可行的,在宣告名稱時如果指定了<?>而 不使用"extends",則預設是允許Object及其下的子類,也就是所有的Java物件了,那為什麼不直接使用GenericFoo宣告就好了,何 必要用GenericFoo<?>來宣告?使用通配字元有點要注意的是,透過使用通配字元宣告的名稱所參考的物件,您沒辦法再對它加入新的資 訊,您只能取得它的資訊或是移除它的資訊,例如:
GenericFoo<String> foo = new GenericFoo<String>();
foo.setFoo("caterpillar");
GenericFoo<?> immutableFoo = foo;

// 可以取得資訊
System.out.println(immutableFoo.getFoo());

// 可透過immutableFoo來移去foo所參考實例內的資訊
immutableFoo.setFoo(null);

// 不可透過immutableFoo來設定新的資訊給foo所參考的實例
// 所以下面這行無法通過編譯
//  immutableFoo.setFoo("良葛格");

所以使用<?>或是<? extends SomeClass>的宣告方式,意味著您只能透過該名稱來取得所參考實例的資訊,或者是移除某些資訊,但不能增加它的資訊,因為只知道當中放置的 是SomeClass的子類,但不確定是什麼類的實例,編譯器不讓您加入物件,理由是,如果可以加入物件的話,那麼您就得記得取回的物件實例是什麼形態, 然後轉換為原來的型態方可進行操作,這就失去了使用泛型的意義。

事實上,GenericFoo<?> immutableFoo相當於GenericFoo immutableFoo


除了可以向下限制,您也可以向上限制,只要使用"super"關鍵字,例如:
GenericFoo<? super StringBuilder> foo;
 
如此,foo就只接受 StringBuilder 及其上層的父類型態之物件。