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不应该尊重SedimentaryMineral
是IMineral
的一个内容并允许这样做的事实吗?!?
除了解释为什么我得到这个编译器错误,也许有人可以指出为什么我的方法是“糟糕的设计”以及我应该做些什么来纠正它。提前谢谢!
答案 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类型的集合(但确切类型可能会有所不同)。