造成這種情況的原因是基於Java如何實現泛型。我發現解釋它的最好方法是精確比較數組和泛型集合。
的陣列實例
使用數組,你可以這樣做:
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
但是,如果你嘗試這樣做,會發生什麼?
Number[0] = 3.14; //attempt of heap pollution
最後一行將編譯就好了,但如果你運行這段代碼,你可以得到一個ArrayStoreException
。
這意味着你可以欺騙編譯器,但你不能欺騙運行時類型系統。這是因爲數組是我們所說reifiable類型。這意味着在運行時Java知道這個數組實際上是作爲一個整數數組實例化的,而這個數組恰恰恰好通過Number[]
類型的引用來訪問。
所以,正如你所看到的,一件事是對象的真實類型,另一件事是你用來訪問它的引用的類型,對吧?
與Java泛型
現在的問題,與Java泛型類型的問題是類型信息被編譯器丟棄,它不是在運行時可用。這個過程被稱爲type erasure。在Java中實現類似這樣的泛型有很好的理由,但這是一個很長的故事,並且它與二進制兼容性與已有的代碼有關。
但是重要的一點是,因爲在運行時沒有類型信息,所以沒有辦法確保我們不會造成堆污染。
例如,
List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts;
myNums.add(3.14); //heap polution
如果Java編譯器不會在編譯時做這個阻止你,運行時類型系統無法阻止你要麼,因爲沒有辦法,在運行時間,以確定這個列表應該只是一個整數列表。 Java運行時會讓你把你想要的東西放到這個列表中,當它只包含整數時,因爲它在創建時被聲明爲一個整數列表。
因此,Java的設計者確保你不能愚弄編譯器。如果你不能欺騙編譯器(就像我們可以對數組做的那樣),你也不能欺騙運行時類型系統。
因此,我們說通用類型是不可確定的。
很明顯,這會妨礙pollymorphism以及指出。解決方案是學習使用稱爲協變和逆變的Java泛型的兩個強大功能。
協方差
協方差可以從結構看項目,但你不能寫任何東西進去。所有這些都是有效的聲明。
List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>()
List<? extends Number> myNums = new ArrayList<Double>()
而且你可以從myNums
閱讀:
Number n = myNums.get(0);
因爲你可以肯定,無論實際的列表中包含,可以upcasted的號碼(後擴展號碼的所有東西是Number ,對不對?)
但是,您不允許將任何東西放入協變結構中。
myNumst.add(45L);
這將不被允許,因爲Java不能保證真實對象的實際類型是什麼。它可以是擴展Number的任何東西,但編譯器不能確定。所以你可以閱讀,但不能寫。
逆變
隨着逆變你可以做相反的事情。你可以把東西放到一個通用的結構中,但是你不能從中讀出。
List<Object> myObjs = new List<Object();
myObjs.add("Luke");
myObjs.add("Obi-wan");
List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);
在這種情況下,對象的實際性質是對象的名單,並通過逆變,你可以把數字進去,主要是因爲數字具有對象的共同祖先。因此,所有的數字都是對象,因此這是有效的。
然而,假設你會得到一個數字,你無法從這個逆變結構中安全地讀取任何東西。如你所見,如果編譯器允許你寫這行,你將在運行時得到一個ClassCastException。
獲取/放置原理
因此,使用協方差,當你只打算採取的具體數值出的結構,使用逆變當你只打算把通用值到結構,並使用精確通用當你打算同時做這兩件事時鍵入。
我最好的例子是將任何一種數字從一個列表複製到另一個列表中。
public static void copy(List<? extends Number> source, List<? super Number> destiny) {
for(Number number : source) {
destiny.add(number);
}
}
由於協變和逆變的權力,這適用於這樣的情況:
List<Integer> myInts = asList(1,2,3,4);
List<Integer> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();
copy(myInts, myObjs);
copy(myDoubles, myObjs);
要枚舉的'Collection'使用'EnumSet.allOf(Module.class)'代替。 – viktor
如果您有問題的解決方案,那麼您可能應該發佈了與評論相比的答案,這樣您才能真正獲得投票。其他答案提供更簡單的解決方案。 –