4

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

我能做些什么 ?

感谢并为我不太流利的英语感到抱歉;)

4

5 回答 5

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>>.

另外,请阅读这篇关于有界通配符参数以及何时避免使用它们的文章。

于 2009-12-15T17:20:56.527 回答
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将允许您返回正确的类型。

于 2009-12-15T17:22:01.250 回答
1

问题是编译器无法知道您从中获取的是否是您AbstractItem从中获取myItems的正确类型。您可能正在尝试将 a放入 aMyMotherClassmyCollectionConcreteItem2MyMotherClass<ConcreteItem1>

处理此问题的一种方法是将您的 as 定义myCollectionMap<String, MyMotherClass<AbstractItem>>. 然后,您就可以将 anyAbstractItem放入您从中获得的任何对象中myCollection。但是,您将失去ConcreteItemgetItem().

我不认为 Alexander Pogrebnyak 的建议在这种情况下真的有效。他的想法基本上等同于使用,MyMotherClass<AbstractItem>因为在您的情况下,类型参数必须扩展AbstractItem并且他正在声明它? super AbstractItem,唯一满足两者的类就是AbstractItem它自己。

根据您的设置方式,您可能可以使用Class对象执行某些操作。例如,如果您的myCollection地图还包含一个Class对象,每个对象MyMotherClass代表它所拥有的项目的类型,那么您可以使用它来转换项目,甚至可以让myItems地图除了字符串之外还按类型键控。

于 2009-12-15T17:56:21.383 回答
0

补充:官方参考链接

Sun 说(来自在 J2SE 5.0 中使用和编程泛型

通配符分为三种类型:

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

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

于 2009-12-15T17:37:04.400 回答
-2

您的问题没有答案,因此您应该接受我的回答。

如果甚至不知道项目的实际类型,编译器怎么会知道?你试图将一个你不知道类型的项目插入一个你不知道项目类型的容器中,你是在如履薄冰。

有时,程序员确实比编译器更了解运行时数据类型,在这种情况下,强制转换和抑制警告对于让编译器平静下来是必要的。

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

于 2009-12-16T01:28:44.860 回答