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;
    }
}

Getting a compiler error:

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

I understand that I can't convert an impl back to its API interface (because an API is just than - an API). But I'm confused as to why I'm getting a compiler error! Shouldn't Java honor the fact that SedimentaryMineral is an impl of IMineral and allow this?!?

Along with an explanation as to why I'm getting this compiler error, perhaps someone could point out why my approach here is "bad design" and what I should do to correct it. Thanks in advance!

IAmYourFaja
  • 55,468
  • 181
  • 466
  • 756
  • 3
    There's a really good explanation in the answer of this question: http://stackoverflow.com/questions/5082044/most-efficient-way-to-cast-listsubclass-to-listbaseclass – Riley Lark Apr 09 '12 at 15:50

5 Answers5

6

Imagine if this compiled:

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?
}

You have a serious issue there.

What you can do is this: List<? extends IMineral> mienralList = list

Jeffrey
  • 44,417
  • 8
  • 90
  • 141
2

The problem is that Java generics are not covariant; List<SedimentaryMineral> does not extend/implement List<IMineral>.

The solution depends on precisely what you wish to do here. One solution would involve wildcards, but they impose certain limitations.

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • See my answer for a simple example why a collection of apples is not a collection of fruit. Even if collections were covariant, you'd just risk getting runtime exceptions. I prefer compile time error checking to unexpected errors at runtime. – Has QUIT--Anony-Mousse Apr 09 '12 at 16:05
1

Here is what will work for you:

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;
    }
}

Here I am using wildcard to denote that I allow list of everything that extends the basic interface to be returned from getMinerals. Note that I also changed some of your classes to interfaces so that everything will compile (I also removed the accessors of the classes so that I can put them in a single file, but you can add them back).

Boris Strandjev
  • 46,145
  • 15
  • 108
  • 135
0

First, your code would work if you done something like

...
public interface IRock
{
    public List<? extends IMineral> getMinerals();
}
...

Second, you can't do this directly because you wouldn't be able to guarantee type safety from what you insert inside your list. So, if you want anything that could extend Mineral inside your rock, do what I showed above. If you want that only a specific type of be inserted inside a rock, do something like

public interface IRock<M extends IMineral> {
    public List<M> getMinerals();
}
public class SedimentaryRock implements IRock<SedimentaryMineral> {
   public List<SedimentaryMineral> getMinerals()
   {
    return minerals;
   }
}
Caesar Ralf
  • 2,203
  • 1
  • 18
  • 36
0

You need to understand why this cannot work in general, and why it is a good thing to have the compiler complain here.

Assuming we have a class ParkingLot implements Collection<Cars> and since Car extends Vehicle, this would automatically make the ParkingLot also implement Collection<Vehicle>. Then I could put my Submarine into the ParkingLot.

Less funny, but simpler speaking: a collection of apples is not a collection of fruit. A collection of fruit may contain bananas, while a collection of apples may not.

There is a way out of this: using wildcards. A collection of apples is a collection of "a particular subtype of fruit". By forgetting which kind of fruit it was, you get what you intended: you know it's some kind of fruit you get out. At the same time, you can't be sure that you are allowed to put in arbitrary fruit.

In java, this is

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

Let me emphasize the difference again:

Collection<Fruit> collection_of_arbitrary_fruit = ...;
collection_of_arbitrary_fruit.put(new Apple());
collection_of_arbitrary_fruit.put(new Banana());

Must be able to store any fruit, apples and bananas.

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.

Could be a collection of apples, a collection of banananas, a collection of green apples only, or a collection of arbitary fruit. You don't know which type of fruit, could be a mix. But they're all Fruit.

In read-only situations, I clearly recommend using the second approach, as it allows both specialized ("bag of apples only") and broad collections ("bag of mixed fruit")

Key to understanding this is to read Collection<A> as Collection of different kind of A, while Collection<? extends A> is a Collection of some subtype of A (the exact type however may vary).

Has QUIT--Anony-Mousse
  • 76,138
  • 12
  • 138
  • 194