2015-01-08 42 views
1

編譯下面的代碼失敗:在Java泛型是不變的?

public static void swap(List<?> list, int i, int j) { 
     list.set(i, list.set(j, list.get(i))); 
} 

,如:

Swap.java:5: set(int,capture#282 of ?) in List<capture#282 of ?> cannot be applied to (int,Object) 
      list.set(i, list.set(j, list.get(i))); 

但是,如果我這樣做:

public static void swap(List<?> list, int i, int j) { 
     swapHelper(list, i, j); 
} 

private static <E> void swapHelper(List<E> list, int i, int j) { list.set(i, list.set(j, list.get(i))); 
} 

其完美的工作。

但我在這裏有一個基本的疑問。泛型被認爲是不變的,所以List<String>不是List<Object>的子類型,對吧?

如果是這樣,那麼如何在上述方法中,我們能夠通過List<?>List<E>?這是如何工作的?

+0

通配符的工作方式與標準is-it-possible-to-assign檢查不同。 – hexafraction

回答

7

答案是通配符。

List<?>是不一樣的List<Object> 而Java假定都是對象的集合,前者將匹配任何其他List<T>類型,而後者將不會匹配任何List<T>的類型除了List<Object>

例如:

public void doSomething(List<Object> list); 

此功能將只接受List<Object>,因爲它的參數。但是這個:

public void doSomething(List<?> list); 

將接受任何List<T>作爲它的參數。

當與generic constraints一起使用時,這可能非常有用。例如,如果你想編寫一個操縱數的函數(Integer S,Float S等),你可以:

public void doSomethingToNumbers(List<? extends Number> numbers) { ... } 
+0

真的不是「非常有用」,更像是一個簡寫:'public void doSomethingToNumbers(List <?extends Number> numbers)'與 public void doSomething(List numbers)' – Dima

+0

這也是非常有用。不過,我相信這個例子中的通配符語法更清晰,更好地傳達了程序員的意圖。 – Assaf

1

由於您使用wildcard捕獲。

在某些情況下,編譯器推斷通配符的類型。例如,可以將列表定義爲列表,但是當評估表達式時,編譯器從代碼中推斷特定類型。這種 方案被稱爲通配符捕獲。

感謝助手方法,編譯器使用推理來確定調用中的捕獲變量T.

0

List<?>表示「未知類型的列表」。你可以通過一個List<String>,不是因爲它是一個子類(不是),而是因爲「未知類型」應該接受你拋出的任何東西。

由於類型未知,因此無法執行操作,需要知道元素的類型(如setadd等)。

List<E>是一個通用的。它與List<?>不同,即使它看起來很相似。 正如您所指出的,一個區別是您可以對它執行諸如setadd之類的操作,因爲元素的類型現在已知。

作爲函數參數的Whildcards並不是非常有用(<E> void foo(List<E> l)void foo(List<?> l)沒有太大區別)。他們也繞過了所有的編譯時類型檢查,從而擊敗了泛型的目的,而且應該避免。

更常見的(和危害較小)使用通配符在返回類型:List<? extends DataIterface> getData()意味着getData返回實現DataInterface一些對象的列表,但不會告訴你他們的具體類型。這通常是在API設計中完成的,以從接口中分離實現細節。慣例通常是,您可以將API返回的對象列表傳遞給其他API方法,並且它們將接受並處理它們。這個概念被稱爲existential types

另外,還要注意getData從上面的例子可以返回不同類型的列表,這取決於一些條件,即主叫方的域之外,不同於如果它被聲明爲返回List<E>,在這種情況下,調用者將有以某種方式指定預期的類型。