Java Generics: Cannot cast List<SubClass> to List<SuperClass>?

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.

Tags:

Java

Generics