Java集合协方差问题

Mat*_*zyk 10 java generics collections design-patterns

假设我们有一个包含这些类的程序:

public interface AbstractItem {
}
public SharpItem implements AbstractItem {
}
public BluntItem implements AbstractItem {
}

public interface AbstractToolbox {
    //well the problem starts here...
    public List<AbstractItem> getItems();
}
public ExpensiveToolbox implements AbstractToolbox {
    private List<SharpItem> items = new ArrayList()<SharpItems>;
    public List<SharpItem> getItems() { return this.items; }
}
public CheapTooblox implements AbstractToolbox {
    private List<BluntItem> items = new ArrayList()<BluntItem>;
    public List<BluntItem> getItems() { return this.items; }
}
Run Code Online (Sandbox Code Playgroud)

容易,对吗?好吧,我们现在想要制作一个这样的方法(在一些随机类中):

public void doImportantStuff(AbstractToolbox toolbox) {
//important stuff!
//this obviously won't work
    List<AbstractToolbox> items = toolbox.getItems();
//do some stuffwith all items
}
Run Code Online (Sandbox Code Playgroud)

现在问题是在Java集合中使用泛型不是协变的(希望这是我正在寻找的术语)而且我不能分配ArrayList<ExpensiveToolbox>给a List<AbstractToolbox>.我在这里可以看到的唯一解决方案是复制代码并为每种类型执行一个版本,但这显然很糟糕(如果我们有更多的类用不同的列表实现AbstractToolbox会怎么样?).哦,显然第二个解决方案是放弃泛型并制作一个普通的List,但这是一个好习惯吗?

是否有任何设计模式/实践来解决这些问题?

@Edit:好的,所以我可能不够准确.我希望扩展AbstractToolbox的所有类都有一个扩展AbstractItem的某些类的List然后我想要一个方法,它将AbstractToolbox作为参数并对其列表中的项执行某些操作(使用将在其中定义的类) AbstractItem所以每个可能列表中的所有项目实际上都有它们).

Ste*_*ker 22

您可能需要查看使用通配符类型进行泛型.这是一个快速链接: 什么是PECS(生产者扩展消费者超级)?

快速回答:将类型更改为 List<? extends AbstractItem>

你为什么不能分配这个?

想象一下这里的代码......

List<AbstractItem> foo = new ArrayList<SharpItem>();
foo.add(new BluntItem());
Run Code Online (Sandbox Code Playgroud)

静态类型说这应该工作......但你不能这样做!它会违反ArrayList的类型.这就是为什么不允许这样做的原因.如果你改成它

List<? extends AbstractItem> foo = new ArrayList<SharpItem>();
Run Code Online (Sandbox Code Playgroud)

然后,您可以执行分配,但永远不会向列表中添加任何内容.您仍然可以从列表中检索元素,如AbstractItems.

只是使用List(裸型)一个很好的解决方案?

不,绝对不是:-p

  • 或者,或者,使用类型参数<T extends AbstractItem>键入AbstractToolbox并使用它. (3认同)

Mar*_*ers 5

这里有一些额外的想法。一切保持不变,但使用以下方法:

interface AbstractToolbox {
    public List<? extends AbstractItem> getItems();
}
Run Code Online (Sandbox Code Playgroud)

这基本上说抽象类的项是未知类型,但是子类可以使其具体。这将要求您调用getItems()ExpensiveToolbox或CheapToolbox类型的引用,以便能够检索允许您添加项目的列表,等等。

ExpensiveToolbox toolbox = new ExpensiveToolbox();
AbstractToolbox absTB = toolbox;

List<? extends AbstractItem> items1 = absTB.getItems(); //fine
List<SharpItem> items2 = absTB.getItems(); //compile error
List<SharpItem> items3= toolbox.getItems(); //fine
Run Code Online (Sandbox Code Playgroud)

或者,您可以键入AbstractToolbox:

public interface AbstractToolbox<T extends AbstractItem> {
    public List<T> getItems();
}
public ExpensiveToolbox implements AbstractToolbox<SharpItem> {
    public List<SharpItem> getItems() { //...
}
Run Code Online (Sandbox Code Playgroud)