This question already has an answer here:
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?
List<DataNode>
does not extendList<Tree>
even thoughDataNode
extendsTree
. That's because after your code you could do b1.add(SomeTreeThatsNotADataNode), and that would be a problem since then a1 would have an element that is not a DataNode in it as well.You need to use wildcard to achieve something like this
On the other hand
DataNode[]
DOES extendTree[]
. At the time it seemed like the logical thing to do, but you can do something like:This is why when they added generics to Collections they chose to do it a little differently to prevent runtime errors.
This is a classic problem with generics implemented with type erasure.
Suppose that your first example really did work. You would then be able to do the following:
But since
b1
anda1
refer to the same object, it means thata1
now refers to aList
that holds bothDataNode
s andTree
s. If you try to get that last element, you will get an exception (can't remember which one).The short explanation: it was a mistake to allow it originally for Arrays.
The longer explanation:
Suppose this were allowed:
Then couldn't I proceed to:
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 (likeList
) 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 aList<A>
to aList<B>
unlessA
andB
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.Short answer: List a1 is not the same type as List b2; In a1 you can put any objecttype wichs extens DataNode. So it may contain other types than Tree.
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:
What would you expect to happen?
You can do this:
... because then you can only fetch things from
b1
, and they're guaranteed to be compatible withTree
. You can't callb1.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.
When arrays were designed (i.e. pretty much when java was designed) the developers decided that variance would be useful, so they allowed it. However this decision was often criticized because it allows you to do this (assume that
NotADataNode
is another subclass ofTree
):So when generics were designed it was decided, that generic data structures should only allow explicit variance. I.e. you can't do
List<Tree> b1 = a1;
, but you can doList<? extends Tree> b1 = a1;
.However if you do the latter, trying to use the
add
orset
method (or any other method which takes aT
as an argument) will cause a compile error. This way it is not possible to make the equivalent of the above array problem compile (without unsafe casts).