2017-04-15 64 views
3

我有一個非常基本的問題。在理解中掙扎<? extends T> Java中的通配符

下面的代碼無法編譯(假設蘋果擴展水果):

List<? extends Fruit> numbers = new ArrayList<>(); 
    numbers.add(new Apple()); //compile time error 

當看到有關爲什麼不呢,我不懂的話,但沒有這個概念:)。

我們假設第一個水果不是抽象類。我明白,因爲我們正在處理多種子類型,所有這些擴展了Fruit。據推測,由於我們無法確定水果的確切類型,因此我們不能在該系列中添加任何東西。有幾件事我不明白:

1)顯然我們無法知道它是哪個水果讓我困惑。在迭代集合的時候,我們不能通過類型檢查或其他實例檢查來告訴特定類型嗎?

2)假設Fruit是一個具體類,爲什麼我們不允許添加Fruit的實例?這似乎是有道理的,因爲你至少會知道Fruit的API。即使你不知道Fruit的確切子類型,至少你可以在Fruit()上調用標準方法。

我覺得這應該是相當明顯的,但有些東西不是我的點擊。任何幫助表示感謝。謝謝!

+1

不應該是'new Apple()'(即括號丟失)嗎? – 5ar

+0

http://stackoverflow.com/questions/12292752/java-wildcards-and-generics-super-t-and-extends-t?rq=1 – adeady

+0

可能的重複[Java通配符和泛型?超級T和?擴展T](http://stackoverflow.com/questions/12292752/java-wildcards-and-generics-super-t-and-extends-t) – adeady

回答

4

理解這一點的最好方法是將通配符看作是關於列表而不是水果。換句話說:

​​

當我們通過allBananasenumerateMyFruit,在方法內我們失去了對原聲明的類型列表中的信息。在這個例子中,我們可以很清楚地看到爲什麼我們不應該能夠將蘋果放在List<? extends Fruit>中,因爲我們知道該列表實際上是List<Banana>。同樣,通配符告訴我們有關列表的聲明類型。

List<? extends Fruit>應被理解爲類似「或原本宣稱持有Fruit列表Fruit一些亞型,但我們不知道聲明的類型是什麼了。」我們所知道的是,我們從列表中抽出的所有內容都是Fruit

此外,你是對的,我們可以迭代列表並使用instanceof找出真正在列表中的內容,但是這不會告訴我們列表的原始聲明類型。在上面的代碼片段中,我們會發現列表中的所有內容都是Banana,但我可以簡單地將allBananas聲明爲List<Fruit>


您可能還會看到why a List<Dog> is not a List<Animal>,它解釋了這一點。通配符是我們在泛型中的協方差。一個List<Dog>不是List<Animal>,但它是一個List<? extends Animal>。這與限制,我們不能添加到List<? extends Animal>,因爲它可能是List<Dog>List<Cat>或其他東西。我們不知道了。

還有? super,which is the opposite。我們可以將Fruit存儲在List<? super Fruit>中,但我們不知道我們將從中抽取哪些對象。它最初的聲明類型實際上可能是一個List<Object>,其中有各種其他的東西。

3

首先要記住,對於沒有通配符的泛型參數,不能用一個換另一個。如果一個方法需要List<Fruit>它不會需要List<Apple>,它必須是完全匹配的。另外請記住這是關於變量的靜態類型,沒有直接連接到內容。即使你的List<Fruit>包含所有的蘋果,你仍然不能用List<Apple>代替它。 所以我們討論的是類型聲明,而不是集合中的內容。

還記得instanceof是在運行時完成的,泛型在編譯時工作。泛型是關於幫助編譯器弄清楚什麼類型的東西,所以你不必訴諸於instanceof和cast。

當方法foo採取與一般類型List<? extends Fruit>一個參數,那就是說,該方法可採取的範圍內的類型,在這種情況下是的那些下列任何一種方式:

  • 你可以通過在List<Fruit>

  • 您可以通過一個List<Banana>

  • 您可以通過在List<Apple>

(等等,不論什麼Fruit亞型你)

所以你的方法可以與任何這些名單的工作,但是,這種方法的身體已經有效期爲任何人。當您將Apple添加到列表中時,適用於傳入的內容是List<Apple>的情況,它適用於List<Fruit>,對於List<Banana>不是那麼有用。 (與製作水果混凝土於事無補, 加入Fruit沒有爲List<Apple>情況下工作,要麼。)

這就是爲什麼有這麼隨時隨地通配符類型擴展一些規則,添加的東西是不可能的,它不可能適用於所有可能傳入的類型。