Java泛型类和通配符

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

我该做什么?
谢谢,对于我的英语不是很流利,很抱歉;)
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>>

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


是的!它解决了警告问题,但又引入了一个新问题: myCollection.put("key", new MyChildClass()); 无法编译(签名不匹配) 我该如何解决? - Jerome Cance
1
正如Mike McNertney所解释的那样 - 以及你已经发现的 - 这实际上并没有解决你的问题; 实际上恰恰相反,因为你通配符的意思是你的类是AbstractItem的超类,而不是子类。这毫无意义。如果我可以这么大胆,给我的建议一个尝试 - 将MyMotherClass#setItem()更改为期望AbstractItem,并将MyMotherClass#getItem()返回AbstractItem。然后更改MyChildClass的相应方法 - 您的MyMotherClass的子类 - 以期望/返回正确的类。 - delfuego
无论是super还是extend,都将类型固定为AbstractItem。这本身并没有错,但却违背了泛型的初衷。 - irreputable

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的所有子类,采用同样的方法可以让你返回正确的类型。


我理解你的意思,但是我不明白泛型类的目的。因为我们只是在谈论setItem,而我有很多函数使用泛型参数作为返回值,我不想在子类中覆盖每个函数以进行强制类型转换。对吧? - Jerome Cance
所以你不必这样做 -- 你总是可以返回一个子类作为更通用的超类,从中派生。只有在重要的情况下,子类方法声明它仅返回您的抽象类的特定子类时,覆盖子类方法才很重要。 - delfuego
是的,对于我的一些函数来说确实是这样。但无论如何,我会使用你的解决方案。感谢你的所有回复。我验证了你的回答。 - Jerome Cance

1
问题在于编译器无法知道从myItems获取的AbstractItem是否是从myCollection获取的MyMotherClass所需的正确类型。您可能正在尝试将ConcreteItem2放入MyMotherClass<ConcreteItem1>中。
处理此问题的一种方法是将myCollection定义为Map<String,MyMotherClass<AbstractItem>>。然后,您可以将任何AbstractItem放入从myCollection获取的任何对象中。但是,您将失去从getItem()获取ConcreteItem的能力。
我认为Alexander Pogrebnyak的建议在这种情况下并不起作用。他的想法基本上相当于只使用MyMotherClass<AbstractItem>,因为在您的情况下,类型参数必须扩展AbstractItem,而他声明它为? super AbstractItem,唯一满足两者要求的类就是AbstractItem本身。
根据您的设置方式,您可能可以使用一个Class对象来完成某些操作。例如,如果您的myCollection映射还包括每个MyMotherClassClass对象,表示它所持有的项目类型,那么您可以使用它来转换项目,甚至可以将myItems映射按类型键入,而不仅仅是字符串。

1
这基本上是我在下面回答中实现的概念...... MyMotherClass 的子类都会覆盖 setType 期望的类型并由 getType 返回。 - delfuego

0

附加内容:官方参考链接

Sun公司表示(来自在J2SE 5.0中使用和编程泛型):

有三种通配符:

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

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


1
这并没有什么意义--他不是在寻找AbstractItem的超类,而是在相关方法签名中仍在寻找它的子类。 - delfuego

-2

对于你的问题,目前没有答案,因此你应该接受我的答案。

如果你连项目的实际类型都不知道,编译器怎么可能知道呢?你试图将一个类型未知的项目插入到一个类型未知的容器中,这就像是在薄冰上行走。

有时候程序员对运行时数据类型的了解比编译器更多,这种情况下需要进行强制转换和抑制警告来平息编译器。

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


-1:实际上,在许多情况下它可以。这就是泛型的全部意义所在。 - Jørgen Fogh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接