使用泛型的Java编译错误

时间:2012-04-09 15:47:53

标签: java generics inheritance

public class IRock
{
    public List<IMineral> getMinerals();
}

public class IMineral { ... }

public class SedimentaryMineral implements IMineral { ... }

public class SedimentaryRock implements IRock
{
    private List<SedimentaryMineral> minerals;

    @Override
    public List<SedimentaryMineral> getMinerals()
    {
        return minerals;
    }
}

获取编译器错误:

Type mismatch: cannot convert from List<SedimentaryMineral> to List<IMineral>.

我知道我无法将impl转换回其API接口(因为API只是 - 一个API)。但我很困惑为什么我收到编译器错误! Java不应该尊重SedimentaryMineralIMineral的一个内容并允许这样做的事实吗?!?

除了解释为什么我得到这个编译器错误,也许有人可以指出为什么我的方法是“糟糕的设计”以及我应该做些什么来纠正它。提前谢谢!

5 个答案:

答案 0 :(得分:6)

想象一下,如果编译:

List<SedementaryMineral> list = new ArrayList<>();
list.put(new SedimentaryMineral());

List<IMineral> mineralList = list;
mineralList.add(new NonSedimentaryMineral());

for(SedementaryMineral m : list) {
    System.out.println(m); // what happens when it gets to the NonSedimentaryMineral?
}

那里有一个严重的问题。

您可以做的是:List<? extends IMineral> mienralList = list

答案 1 :(得分:2)

问题是Java泛型are not covariant; List<SedimentaryMineral>不会扩展/实施List<IMineral>

解决方案取决于您希望在此处做什么。一种解决方案涉及wildcards,但它们会带来某些限制。

答案 2 :(得分:1)

以下是适用于您的内容:

interface IRock
{
    public List<? extends IMineral> getMinerals();
}

interface IMineral { }

class SedimentaryMineral implements IMineral {  }

class SedimentaryRock implements IRock
{
    private List<SedimentaryMineral> minerals;

    public List<? extends IMineral> getMinerals()
    {
        return minerals;
    }
}

这里我使用通配符来表示我允许从getMinerals返回扩展基本接口的所有内容的列表。请注意,我还将一些类更改为接口,以便编译所有内容(我还删除了类的访问器,以便我可以将它们放在一个文件中,但您可以将它们添加回来)。

答案 3 :(得分:0)

首先,如果你做了像

这样的事情,你的代码就可以了
...
public interface IRock
{
    public List<? extends IMineral> getMinerals();
}
...

其次,您不能直接执行此操作,因为您无法保证您在列表中插入的内容的类型安全性。所以,如果你想要任何可以在你的岩石中扩展矿物质的东西,做我上面展示的。如果你想只在岩石中插入一种特定的类型,那就做一些像

这样的事情
public interface IRock<M extends IMineral> {
    public List<M> getMinerals();
}
public class SedimentaryRock implements IRock<SedimentaryMineral> {
   public List<SedimentaryMineral> getMinerals()
   {
    return minerals;
   }
}

答案 4 :(得分:0)

你需要理解为什么这不能正常工作,以及为什么编译器在这里抱怨是一件好事。

假设我们有一个班级ParkingLot implements Collection<Cars>,而且距离Car extends Vehicle,这会自动使ParkingLot也实现Collection<Vehicle>。然后我可以将Submarine放入ParkingLot

不那么有趣,但更简单的说法:苹果的集合不是水果的集合。水果的集合可能包含香蕉,而苹果的集合可能不包含。

有一种方法可以解决这个问题:使用通配符。苹果的集合是“特定的水果亚型”的集合。通过忘记它是哪种水果,你得到你想要的东西:你知道它是你出去的某种水果。与此同时,你无法确定是否允许任意生果。

在java中,这是

Collection<? extends Fruit> collectionOfFruit = bagOfApples;
// Valid, as the return is of type "? extends Fruit"
Fruit something = collectionOfFruit.iterator().next();
// Not valid, as it could be the wrong kind of fruit:
collectionOfFruit.put(new Banana());
// To properly insert, insert into the bag of apples,
// Or use a collection of *arbitrary* fruit

让我再次强调差异:

Collection<Fruit> collection_of_arbitrary_fruit = ...;
collection_of_arbitrary_fruit.put(new Apple());
collection_of_arbitrary_fruit.put(new Banana());

必须能够存储任何水果,苹果和香蕉。

Collection<? extends Fruit> collection_of_one_unknown_kind_of_fruit = ...;
// NO SAFE WAY OF ADDING OBJECTS, as we don't know the type
// But if you want to just *get* Fruit, this is the way to go.

可以是苹果,香蕉,一系列青苹果或一系列任意水果的集合。你不知道哪种类型的水果,可能是混合物。但他们都是水果。

在只读情况下,我明确建议使用第二种方法,因为它允许专门(“仅苹果袋”)和广泛收集(“混合水果袋”)

理解这一点的关键是将Collection<A>读作收集不同类型的A ,而Collection<? extends A>某个A类型的集合(但确切类型可能会有所不同)。