Reputation: 56944
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
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
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
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
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
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