Java Generics: Cannot cast List<SubClass> to List<SuperClass>? [duplicate]
Just come across with this problem:
List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = a1; // compile error: incompatible type
Where the type DataNode is a subtype of Tree.
public class DataNode implements Tree
To my surprise, this works for array:
DataNode[] a2 = new DataNode[0];
Tree[] b2 = a2; // this is okay
This likes a bit strange. Can anyone give an explanation on this?
What you're seeing in the second case is array covariance. It's a bad thing IMO, which makes assignments within the array unsafe - they can fail at execution time, despite being fine at compile time.
In the first case, imagine that the code did compile, and was followed by:
b1.add(new SomeOtherTree());
DataNode node = a1.get(0);
What would you expect to happen?
You can do this:
List<DataNode> a1 = new ArrayList<DataNode>();
List<? extends Tree> b1 = a1;
... because then you can only fetch things from b1
, and they're guaranteed to be compatible with Tree
. You can't call b1.add(...)
precisely because the compiler won't know whether it's safe or not.
Have a look at this section of Angelika Langer's Java Generics FAQ for more information.
If you do have to cast from List<DataNode>
to List<Tree>
, and you know it is safe to do so, then an ugly way to achieve this is to do a double-cast:
List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = (List<Tree>) (List<? extends Tree>) a1;
The short explanation: it was a mistake to allow it originally for Arrays.
The longer explanation:
Suppose this were allowed:
List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = a1; // pretend this is allowed
Then couldn't I proceed to:
b1.add(new TreeThatIsntADataNode()); // Hey, b1 is a List<Tree>, so this is fine
for (DataNode dn : a1) {
// Uh-oh! There's stuff in a1 that isn't a DataNode!!
}
Now an ideal solution would allow the kind of cast you want when using a variant of List
that was read-only, but would disallow it when using an interface (like List
) that's read-write. Java doesn't allow that kind of variance notation on generics parameters, (*) but even if it did you wouldn't be able to cast a List<A>
to a List<B>
unless A
and B
were identical.
(*) That is, doesn't allow it when writing classes. You can declare your variable to have the type List<? extends Tree>
, and that's fine.