2012-04-09 67 views
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; 
    } 
} 

获得一个编译器错误错误:的Java编译泛型

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

我明白,我不能转换的IMPL回到它的API接口(因为API只比 - 一个API)。但我很困惑,为什么我得到一个编译器错误! Java不应该承认SedimentaryMineralIMineral的impl并允许这个事实?!?

除了解释为什么我得到这个编译器错误,也许有人可能会指出为什么我的方法在这里是“坏设计”,我应该怎么做来纠正它。提前致谢!

+3

这个问题的答案中有一个非常好的解释:http://stackoverflow.com/questions/5082044/most-efficient-way-to-cast-listsubclass-to-listbaseclass – 2012-04-09 15:50:02

回答

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

2

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

解决方案取决于您想要在此做什么。一种解决方案涉及wildcards,但它们会施加一定的限制。

+0

查看我的答案一个简单的例子为什么一堆苹果不是水果的集合。即使集合是协变的,你也可能冒险得到运行时异常。我更喜欢编译时错误检查在运行时意外的错误。 – 2012-04-09 16:05:36

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返回。请注意,我还将一些类更改为接口,以便所有内容都可以编译(我也删除了类的访问器,以便将它们放入单个文件中,但可以将它们添加回去)。

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; 
    } 
} 
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<? 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(确切类型但可能会有所不同)。