3 回答

TA貢獻1804條經(jīng)驗 獲得超7個贊
您有兩種不同類型的多態(tài)性在這里以一種令人困惑的方式相互作用。
理解這一點的關(guān)鍵在于,除了參數(shù)多態(tài)性(即泛型)之外,還有子類型多態(tài)性,即經(jīng)典的面向?qū)ο蟮摹癷s-a”關(guān)系。
在 Java 中,所有對象都是Object. 所以一個可以包含Object值的容器可以包含任何值。
如果我們像剛才<Object>那樣重寫所有通用邊界,代碼的工作方式相同,顯然如此:
List<Object> objectList = new ArrayList<>();
objectList.add("str1");
List<Object> numberList = objectList;
numberList.add(1);
objectList.add("str2");
for (int i = 0; i < objectList.size(); i++) {
System.out.println(objectList.get(i) + "");
}
具體來說,objectList.get(i) + ""被評估為調(diào)用 的東西objectList.get(i).toString(),并且由于toString()是 的方法Object,無論 中的對象類型如何,它都將起作用objectList。
這是行不通的:
Number number = numberList.get(i); // error!
這是因為,盡管名稱具有誤導(dǎo)性,但numberList不能保證僅包含Number對象,并且實際上可能根本不包含任何Number對象!
讓我們來看看為什么會這樣。
首先我們創(chuàng)建一個對象列表:
List<? super Object> objectList = new ArrayList<>();
這種類型是什么意思?類型的List<? super Object>意思是“某種類型的對象列表,我不能告訴你是什么類型,但我知道它是什么類型,Object或者是”的超類型Object。我們已經(jīng)知道這Object是子類型層次結(jié)構(gòu)的根,所以這實際上與 相同List<Object>:即這個對象只能包含Object對象。
但是……這不太對。列表只能包含Object對象,但Object對象可以是任何東西!runtype 中的實際對象可以是任何類型的子類型Object(因此,除了原始類型之外的任何其他類型),但是將它們放入此列表中,您將無法再分辨它們是哪種類型的對象——他們可以是任何東西。不過,這對于該程序的其余部分所做的事情來說是可以的,因為它需要能夠做的就是調(diào)用toString()對象,并且它可以做到這一點,因為它們都擴展了Object.
現(xiàn)在讓我們看看另一個變量聲明:
List<? super Number> numberList = objectList;
再次,類型List<? super Number>是什么意思?至關(guān)重要的是,它的意思是“某種類型的對象列表,我不能告訴你它是什么類型,但我知道無論它是什么類型,它都是“的一種Number或某種超類型Number”。好吧,在左邊我們有一個“Number或某個超類型Number”的列表,在右邊我們有一個列表Object——顯然Object是一個的超類型,Number所以這個列表是一個列表Object。一切類型檢查(并且,與我最初的評論相反,沒有任何警告)。
所以問題就變成了:為什么 a 可以List<? super Number>包含 a String?因為 aList<? super Number>可以只是 a List<Object>,而 aList<Object>可以包含 aString因為Stringis-a Object。

TA貢獻1798條經(jīng)驗 獲得超7個贊
類型的List<? super Number>
引用可以引用List<Object>
或List<Number>
。通過此引用進行的任何操作都需要使用這些類型中的任何一種。您無法通過List<? super Number>
引用添加字符串,因為該操作僅適用于一種可能的對象類型,但您可以通過List<? super Object>
引用。
AList<? super Object>
只能引用 a List<Object>
。AList<? super Number>
可以指代 aList<Number>
或 a List<Object>
。這是一種更通用的類型,這就是允許賦值的原因。

TA貢獻1797條經(jīng)驗 獲得超6個贊
當(dāng)編譯器看到:
List<? super Number> numberList = objectList;
它首先捕獲通配符。泛型類型變?yōu)?Y = X >Number(意味著Number的具體超類型)。所以我們有:
List<Y> numberList = objectList //with type of List<Object>;
然后編譯器確定Y可以替換為Object。因此,類型是相同的,numberList并且允許指向與 相同的對象objectList。
然后將生成的字節(jié)碼傳遞給運行時系統(tǒng)執(zhí)行。就運行時系統(tǒng)而言,java.util.ArrayList由于類型擦除,兩個列表都具有類型。因此,當(dāng)您將字符串或其他對象放入此容器時,不會引發(fā)運行時異常。
但我也覺得有些地方不太對勁。重新表述你的問題:
編譯器可以做什么來防止這種情況?
請注意,編譯器不得在賦值期間抱怨,因為:
安全實例化原則:實例化具有滿足參數(shù)聲明約束的類型的參數(shù)類不應(yīng)導(dǎo)致錯誤。
我認為這個原則也適用于作業(yè)。賦值不會破壞任何語言規(guī)則,因此編譯器不得引發(fā)錯誤。
因此,唯一可以將程序員從災(zāi)難中拯救出來的地方就是在add操作期間。但是編譯器在那里能做什么呢?如果它因為賦值而不允許add操作objectList,那將破壞其他語言規(guī)則。如果它增強add以支持向 中添加對象numberList,那也會違反其他一些語言規(guī)則。
我想不出任何簡單易行的解決方案不會破壞很多東西來修復(fù)甚至可能不是問題的東西,而程序員當(dāng)然處于決定地位的好位置。
類型檢查器旨在幫助程序員不要取代她。另一個不完美的例子:
public static void main(String[] args) {
Object m = args;
String[] m2 = m; //complains, despite m2 definitely being an String[]
}
PS:我在SO上找到了上面的例子,但不幸的是,我失去了鏈接!
添加回答
舉報