0
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?!?

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

4

5 回答 5

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

于 2012-04-09T15:50:25.593 回答
2

问题是 Java 泛型不是协变的List<SedimentaryMineral>不扩展/实现List<IMineral>.

解决方案完全取决于您希望在这里做什么。一种解决方案将涉及通配符,但它们施加了某些限制。

于 2012-04-09T15:49:54.617 回答
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. 请注意,我还将您的一些类更改为接口,以便一切都可以编译(我还删除了类的访问器,以便可以将它们放在一个文件中,但您可以将它们添加回来)。

于 2012-04-09T15:51:30.253 回答
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;
   }
}
于 2012-04-09T15:59:47.580 回答
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>不同类型 ACollection<? extends A>的Collection ,而是A 的某些子类型的 Collection(但确切的类型可能会有所不同)。

于 2012-04-09T16:03:26.630 回答