[英]Java generic class and wildcards
我在java中遇到泛型類的問題。
我有這門課:
public abstract class MyMotherClass<C extends AbstractItem>
{
private C item;
public void setItem(C item)
{
this.item = item;
}
public C getItem()
{
return item;
}
}
這個類的實現可以是:
public class MyChildClass extends MyMotherClass<ConcreteItem>
{
}
ConcreteItem只是一個擴展AbstractItem(它是抽象的)的簡單類。
所以MyChildClass有一個ConcreteItem,我可以使用:
MyChildClass child = new MyChildClass();
child.setItem(new ConcreteItem());
// automatic cast due to generic class
ConcreteItem item = child.getItem();
好的,目前一切都很好。 這是問題所在:
現在我想從集合中提取MyMotherClass的一個實例並設置它的項目(哪個類型未知):
Map<String, MyMotherClass> myCollection = new HashMap<String, MyMotherClass>();
Map<String, AbstractItem> myItems = new HashMap<String, AbstractItem>();
// fill the 2 collections
...
MyMotherClass child = myCollection.get("key");
child.setItem(myItems.get("key2"));
如果我喜歡這樣,它會運行。 但我有警告,因為MyMotherClass是泛型類型,我不使用泛型類型。 但是我不知道我提取的孩子的類型是哪個,所以我想使用通配符:
Map<String, MyMotherClass<?>> myCollection = new HashMap<String, MyMotherClass<?>>();
Map<String, AbstractItem> myItems = new HashMap<String, AbstractItem>();
// fill the 2 collections
...
MyMotherClass<?> child = myCollection.get("key");
child.setItem(myItems.get("key2"));
這是問題所在:我有一個編譯錯誤,說明:MyMotherClass類型中的方法setItem(捕獲#1-of?)不適用於參數(AbstractItem)
當我嘗試使用繼承的通配符時,同樣的問題:
Map<String, MyMotherClass<? extends AbstractItem>> myCollection = new HashMap<String, MyMotherClass<? extends AbstractItem>>();
Map<String, AbstractItem> myItems = new HashMap<String, AbstractItem>();
// fill the 2 collections
...
MyMotherClass<? extends AbstractItem> child = myCollection.get("key");
child.setItem(myItems.get("key2"));
我能做什么 ?
謝謝,抱歉我的英語不是很流利;)
對於write
操作,您需要一個super
通配符。
final Map<String, MyMotherClass<? super AbstractItem>> myCollection =
new HashMap<String, MyMotherClass<? super AbstractItem>>();
final Map<String, AbstractItem> myItems =
new HashMap<String, AbstractItem>();
...
final MyMotherClass<? super AbstractItem> child =
myCollection.get("key");
child.setItem(myItems.get("key2"));
extends
通配符用於read
操作。
編輯:回應你的評論。
首先,評估您是否真的需要通配符。
如果答案仍然是肯定的,那么您可以將集合初始化為更具體的類型,然后將它們向下轉換為有界通配符,例如
final Map<String, MyChildClass> myInitCollection =
new HashMap<String, MyChildClass>();
final Map<String, ConcreteItem> myInitItems =
new HashMap<String, ConcreteItem>();
myInitCollection.put( "key", new MyChildClass( ) );
final MyMotherClass< ConcreteItem> child =
myInitCollection.get("key");
child.setItem(myInitItems.get("key2"));
final Map<String, ? extends MyMotherClass< ? extends AbstractItem >>
myCollection = myInitCollection;
final Map<String, ? extends AbstractItem> myItems = myInitItems;
但是請注意,myCollection仍然無法安全地轉換為Map<String, MyMotherClass< ? extends AbstractItem>>
Map<String, MyMotherClass< ? extends AbstractItem>>
。
另外,閱讀本文關於有界通配符參數,以及何時避免它們。
我可能會遺漏一些東西,但是為什么不在MyMotherClass類中使用顯式類AbstractItem
而不是泛型類C
?
public abstract class MyMotherClass<C extends AbstractItem> {
private AbstractItem item;
public void setItem(AbstractItem item) {
this.item = item;
}
public AbstractItem getItem() {
return this.item;
}
}
僅此更改將允許您使用通配符方法:
Map<String, MyMotherClass<?>> myCollection = new HashMap<String, MyMotherClass<?>>();
Map<String, AbstractItem> myItems = new HashMap<String, AbstractItem>();
// fill the 2 collections
MyMotherClass<?> child = myCollection.get("key");
child.setItem(myItems.get("key2"));
沒有錯誤。
當然,在MyChildClass
,您可以覆蓋MyMotherClass#getItem()
,如下所示:
@Override
public ConcreteItem getItem() {
return (ConcreteItem) super.getItem();
}
確保返回正確的班級; 對於MyMotherClass
所有子類,這種相同的方法將允許您返回正確的類型。
問題是,編譯器無法知道是否方式AbstractItem
你得到myItems
對於正確的類型MyMotherClass
你得到myCollection
。 您可能正在嘗試將ConcreteItem2
放入MyMotherClass<ConcreteItem1>
處理此問題的一種方法是將myCollection
定義為Map<String, MyMotherClass<AbstractItem>>
。 然后,您就可以將任何AbstractItem
放入從myCollection
獲得的任何對象中。 但是,您將失去從getItem()
獲取ConcreteItem
的能力。
我認為Alexander Pogrebnyak的建議在這種情況下確實無效。 他的想法基本上只相當於使用MyMotherClass<AbstractItem>
因為在你的情況下,type參數必須擴展AbstractItem
並且他正在聲明它? super AbstractItem
? super AbstractItem
,唯一滿足兩者的類是AbstractItem
本身。
根據您設置的方式,您可以使用Class
對象執行某些操作。 例如,如果myCollection
映射還為每個表示其所包含項目類型的MyMotherClass
包含一個Class
對象,則可以使用該對象來轉換項目,或者除了字符串之外,還可以按類型鍵入myItems
映射。
Sun說(來自J2SE 5.0中的Using and Programming Generics )
通配符有三種類型:
- “?extends Type”:表示Type類型的子類型。 這是最有用的通配符
- “?super Type”:表示Type類型的超類型
- “?”:表示所有類型或任何類型的集合
就像@ alexander-pogrebnyak在他的回答中所說,你必須在這里使用super
。
你的問題沒有答案,所以你應該接受我的答案。
如果您甚至不知道項目的實際類型,編譯器怎么知道? 您嘗試將一個您不知道的類型的項目插入到您不知道的項目類型的容器中,您正在薄冰上行走。
有時候程序員比編譯器對運行時數據類型有更多的了解,在這種情況下,需要使用轉換和抑制警告來平息編譯器。
在您的情況下,您甚至不知道數據類型,並且您希望編譯器可以為您神奇地檢查類型。 那是不可能做到的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.