2011-12-09 77 views
2

也許我忽視的東西很容易和明顯...Collection對象的特定類的數組?

我出外

private void render(Collection<Object> rows); 

現在的方法接口,我需要傳遞的對象是一個數組(由枚舉型):

Module[] mods = Module.values(); 
widget.render(mods); 

當然,這並不工作,但爲什麼這不工作:

widget.render(Arrays.asList(mods)) 

原來我的陣列模塊的集合,並且模塊是一個對象...

+0

要枚舉的'Collection'使用'EnumSet.allOf(Module.class)'代替。 – viktor

+0

如果您有問題的解決方案,那麼您可能應該發佈了與評論相比的答案,這樣您才能真正獲得投票。其他答案提供更簡單的解決方案。 –

回答

6

試着改變你的方法簽名:

private void render(Collection<?> rows); 

這是在說你的方法需要Collection與任何類型的的元素,而之前它說的Collection應具體具有Object作爲其類型參數。

使用這樣的通配符將放在如何利用傳遞到方法,特別是關於修改它的Collection限制。如果您需要更詳細的建議,您可能想告訴我們您的render方法正在做什麼。

這是一篇值得一相對於閱讀在Java中使用通配符類別:What is PECS (Producer Extends Consumer Super)?

+1

啊我看到了......所以這意味着我沒有學到一個重要的教訓:在泛型中沒有隱式繼承,即Collection 不是Collection 的子類型...謝謝 – faboolous

+0

默認情況下java中的所有對象擴展對象,這就是爲什麼任何東西都可以傳遞給需要Object參數的方法。因此,對象本身並不是真正將對象傳遞給它的問題,但是如果對象對象不是適當的類,那麼它們的特殊方法和變量以及其他屬性都不可訪問。 –

+0

鑄造*不是連鑄機 –

4

由於Collection<Object>不是Collection<Module>。關於泛型的一個很好的教程是available in PDF version,並且在您使用泛型時必須閱讀。

如果例如第4頁上說明的,在部分Generics and Subtyping此特定情況。

+0

+1用於指出文檔中要閱讀的內容。這部分是第一次閱讀時的真實「當然,我爲什麼沒有這樣想」。 –

+0

他的工作方式完全正常,如果他只是在渲染主體中將每個項目從Object拖放到Module中 –

+0

我接受了Khan的回覆,因爲他是第一個回答非常容易理解的解決方案,但+1指向我一個文件詳細解釋。 – faboolous

0

如果在集合鑄造每個對象模塊,例如:

if(object instanceof Module) 
{ 
    Module m = (Module)object; 
    //Do stuff here with m 
} 

那麼它應該工作以及。否則,其他兩個答案也可以正常工作。

0

造成這種情況的原因是基於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);