From Effective Java by Joshua Bloch,
Cov
Generics are invariant: from JSL 4.10:
...Subtyping does not extend through generic types: T <: U does not imply that
C<T>
<:C<U>
...
and a few lines further, JLS also explains that
Arrays are covariant (first bullet):
4.10.3 Subtyping among Array Types
We can’t write List<Object> l = new ArrayList<String>();
because Java is trying to
protect us from a runtime exception. You might think this would mean that we can’t write
Object[] o = new String[0];
. That isn’t the case. This code does compile:
Integer[] numbers = { new Integer(42)};
Object[] objects = numbers;
objects[0] = "forty two"; // throws ArrayStoreException
Although the code does compile, it throws an exception at runtime. With arrays, Java
knows the type that is allowed in the array. Just because we’ve assigned an Integer[]
to
an Object[]
doesn’t change the fact that Java knows it is really an Integer[]
.
Due to type erasure, we have no such protection for an ArrayList. At runtime, the ArrayList doesn’t know what is allowed in it. Therefore, Java uses the compiler to prevent this situation from coming up in the first place. OK, so why doesn’t Java add this knowledge to ArrayList? The reason is backward compatibility; that is, Java is big on not breaking existing code.
OCP reference.
I think they made a wrong decision at the first place that made array covariant. It breaks the type safety as it described here and they got stuck with that because of backward compatibility and after that they tried to not make the same mistake for generic. And that's one of the reasons that Joshua Bloch prefers lists to arra ys in Item 25 of book "Effective Java(second edition)"
An important feature of parametric types is the ability to write polymorphic algorithms, i.e. algorithms that operate on a data structure regardless of its parameter value, such as Arrays.sort()
.
With generics, that's done with wildcard types:
<E extends Comparable<E>> void sort(E[]);
To be truly useful, wildcard types require wildcard capture, and that requires the notion of a type parameter. None of that was available at the time arrays were added to Java, and makings arrays of reference type covariant permitted a far simpler way to permit polymorphic algorithms:
void sort(Comparable[]);
However, that simplicity opened a loophole in the static type system:
String[] strings = {"hello"};
Object[] objects = strings;
objects[0] = 1; // throws ArrayStoreException
requiring a runtime check of every write access to an array of reference type.
In a nutshell, the newer approach embodied by generics makes the type system more complex, but also more statically type safe, while the older approach was simpler, and less statically type safe. The designers of the language opted for the simpler approach, having more important things to do than closing a small loophole in the type system that rarely causes problems. Later, when Java was established, and the pressing needs taken care of, they had the resources to do it right for generics (but changing it for arrays would have broken existing Java programs).
May be this help:-
Generics are not covariant
Arrays in the Java language are covariant -- which means that if Integer extends Number (which it does), then not only is an Integer also a Number, but an Integer[] is also a Number[]
, and you are free to pass or assign an Integer[]
where a Number[]
is called for. (More formally, if Number is a supertype of Integer, then Number[]
is a supertype of Integer[]
.) You might think the same is true of generic types as well -- that List<Number>
is a supertype of List<Integer>
, and that you can pass a List<Integer>
where a List<Number>
is expected. Unfortunately, it doesn't work that way.
It turns out there's a good reason it doesn't work that way: It would break the type safety generics were supposed to provide. Imagine you could assign a List<Integer>
to a List<Number>
.
Then the following code would allow you to put something that wasn't an Integer into a List<Integer>
:
List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));
Because ln is a List<Number>
, adding a Float to it seems perfectly legal. But if ln were aliased with li
, then it would break the type-safety promise implicit in the definition of li -- that it is a list of integers, which is why generic types cannot be covariant.
My take: When code is expecting an array A[] and you give it B[] where B is a subclass of A, there's only two things to worry about: what happens when you read an array element, and what happens if you write it. So it's not hard to write language rules to ensure that type safety is preserved in all cases (the main rule being that an ArrayStoreException
could be thrown if you try to stick an A into a B[]). For a generic, though, when you declare a class SomeClass<T>
, there can be any number of ways T
is used in the body of the class, and I'm guessing it's just way too complicated to work out all the possible combinations to write rules about when things are allowed and when they aren't.