2012-04-09 3 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>. 

Я понимаю, что я не могу преобразовать осущ обратно в интерфейс API (поскольку API только чем - с API). Но я смущен, почему я получаю ошибку компилятора! Должна ли Java не уважать тот факт, что SedimentaryMineral является имплантом IMineral и разрешает это?!?

Наряду с объяснением того, почему я получаю эту ошибку компилятора, возможно, кто-то может указать, почему мой подход здесь «плохой дизайн» и что я должен сделать, чтобы исправить его. Заранее спасибо!

+3

В ответе на этот вопрос есть действительно хорошее объяснение: http://stackoverflow.com/questions/5082044/most-efficient-way-to-cast-listsubclass-to-listbaseclass –

ответ

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

См. Мой ответ на простой пример почему коллекция яблок - это не коллекция фруктов. Даже если коллекции были ковариантными, вы просто рискуете получить исключения во время выполнения. Я предпочитаю проверку времени компиляции на непредвиденные ошибки во время выполнения. –

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. 

Может быть коллекцией яблок, коллекция banananas, коллекция только зеленых яблок, или сбор плодов произвольных. Вы не знаете, какой тип фруктов, может быть микс. Но они все Фрукты.

только для чтения ситуации, я ясно рекомендую использовать второй подход, поскольку он позволяет как специализированные («мешок яблок только») и широкие коллекции («мешок смешанных фруктов»)

Ключ к пониманию этого читать Collection<A> как Коллекция различного типа A, а Collection<? extends A> - это Коллекция некоторых подтипов A (точный тип может отличаться).