IAmYourFaja
IAmYourFaja

Reputation: 56944

Java compile error with generics

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!

Upvotes: 0

Views: 134

Answers (5)

Has QUIT--Anony-Mousse
Has QUIT--Anony-Mousse

Reputation: 77485

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).

Upvotes: 0

Caesar Ralf
Caesar Ralf

Reputation: 2234

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

Upvotes: 0

Boris Strandjev
Boris Strandjev

Reputation: 46963

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).

Upvotes: 1

Jeffrey
Jeffrey

Reputation: 44808

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

Upvotes: 6

Oliver Charlesworth
Oliver Charlesworth

Reputation: 272792

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.

Upvotes: 2

Related Questions