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.
List<DataNode>
does not extend List<Tree>
even though DataNode
extends Tree
. 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
List<DataNode> a1 = new ArrayList<DataNode>();
List<? extends Tree> b1 = a1;
b1.add(new Tree()); // compiler error, instead of runtime error
On the other hand DataNode[]
DOES extend Tree[]
. At the time it seemed like the logical thing to do, but you can do something like:
DataNode[] a2 = new DataNode[1];
Tree[] b2 = a2; // this is okay
b2[0] = new Tree(); // this will cause ArrayStoreException since b2 is actually a DataNode[] and can't store a Tree
This is why when they added generics to Collections they chose to do it a little differently to prevent runtime errors.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With