Java泛型类和通配符

时间:2009-12-15 17:06:33

标签: java generics wildcard generic-programming

我在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(capture#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"));

我该怎么办?

感谢并抱歉我的英语不是很流利;)

5 个答案:

答案 0 :(得分:6)

对于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>>

另外,请阅读this article关于有界通配符参数,以及何时避免它们。

答案 1 :(得分:2)

我可能会遗漏一些内容,但为什么不在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的所有子类采用相同的方法可以返回正确的类型。

答案 2 :(得分:1)

问题在于,编译器无法知道从AbstractItem获得的myItems是否是MyMotherClassmyCollection得到的正确类型。您可能尝试将ConcreteItem2放入MyMotherClass<ConcreteItem1>

处理此问题的一种方法是将myCollection定义为Map<String, MyMotherClass<AbstractItem>>。然后,您就可以将任何AbstractItem放入从myCollection获得的任何对象中。但是,您将无法从ConcreteItem获得getItem()

我不认为Alexander Pogrebnyak的建议在这种情况下确实有效。他的想法基本上只相当于使用MyMotherClass<AbstractItem>,因为在你的情况下,type参数必须扩展AbstractItem并且他声明它? super AbstractItem,唯一满足两者的类是AbstractItem本身。

根据您设置的方式,您可以使用Class对象执行某些操作。例如,如果您的myCollection地图还为每个代表其所拥有项目类型的Class添加了MyMotherClass个对象,那么您可以使用它来投射该项目,甚至可以使用{{1}除了字符串之外,map键入的类型。

答案 3 :(得分:0)

增加:官方参考的链接

Sun说(来自Using and Programming Generics in J2SE 5.0

  

通配符有三种类型:

     
      
  1. “?extends Type”:表示Type类型的子类型。这是最有用的通配符
  2.   
  3. “?super Type”:表示Type
  4. 类型的超类型系列   
  5. “?”:表示所有类型或任何
  6. 的集合   

就像@ alexander-pogrebnyak在回答中所说,你必须在这里使用super

答案 4 :(得分:-2)

你的问题没有答案,所以你应该接受我的答案。

如果甚至不知道项目的实际类型,编译器怎么知道呢?您尝试将一个您不知道的类型的项目插入到您不知道的项目类型的容器中,您正在薄冰上行走。

程序员有时会比编译器更多地了解运行时数据类型,在这种情况下,需要使用转换和抑制警告来平息编译器。

在您的情况下,您甚至不知道数据类型,并且您希望编译器可以为您神奇地检查类型。这是不可能做到的。