2012-04-09 66 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(確切類型但可能會有所不同)。