From Effective Java by Joshua Bloch,
Arrays differ from generic type in two important ways. First arrays are covariant. Generics are invariant.
Covariant simply means if X is subtype of Y then X[] will also be sub type of Y[]. Arrays are covariant As string is subtype of Object So
String[] is subtype of Object[]
Invariant simply means irrespective of X being subtype of Y or not ,
List<X> will not be subType of List<Y>.
My question is why the decision to make arrays covariant in Java? There are other SO posts such as Why are Arrays invariant, but Lists covariant?, but they seem to be focussed on Scala and I am not able to follow.
Via wikipedia:
Early versions of Java and C# did not include generics (a.k.a. parametric polymorphism).
In such a setting, making arrays invariant rules out useful polymorphic programs.
For example, consider writing a function to shuffle an array, or a function that tests two arrays for equality using the Object.equals method on the elements. The implementation does not depend on the exact type of element stored in the array, so it should be possible to write a single function that works on all types of arrays. It is easy to implement functions of type
boolean equalArrays (Object[] a1, Object[] a2);
void shuffleArray(Object[] a);
However, if array types were treated as invariant, it would only be possible to call these functions on an array of exactly the type Object[]. One could not, for example, shuffle an array of strings.
Therefore, both Java and C# treat array types covariantly. For instance, in C# string[] is a subtype of object[], and in Java String[] is a subtype of Object[].
This answers the question "Why are arrays covariant?", or more accurately, "Why were arrays made covariant at the time?"
When generics were introduced, they were purposefully not made covariant for reasons pointed out in this answer by Jon Skeet:
No, a List<Dog> is not a List<Animal>. Consider what you can do with a List<Animal> - you can add any animal to it... including a cat. Now, can you logically add a cat to a litter of puppies? Absolutely not.
// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new List<Dog>();
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?
Suddenly you have a very confused cat.
The original motivation for making arrays covariant described in the wikipedia article didn't apply to generics because wildcards made the expression of covariance (and contravariance) possible, for example:
boolean equalLists(List<?> l1, List<?> l2);
void shuffleList(List<?> l);
The reason is that every array knows its element type during runtime, while generic collection doesn't because of type erasure.
For example:
String[] strings = new String[2];
Object[] objects = strings; // valid, String[] is Object[]
objects[0] = 12; // error, would cause java.lang.ArrayStoreException: java.lang.Integer during runtime
If this was allowed with generic collections:
List<String> strings = new ArrayList<String>();
List<Object> objects = strings; // let's say it is valid
objects.add(12); // invalid, Integer should not be put into List<String> but there is no information during runtime to catch this
But this would cause problems later when someone would try to access the list:
String first = strings.get(0); // would cause ClassCastException, trying to assign 12 to String
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.
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).
Arrays are covariant for at least two reasons:
It is useful for collections that hold information which will never change to be covariant. For a collection of T to be covariant, its backing store must also be covariant. While one could design an immutable T collection which did not use a T[] as its backing store (e.g. using a tree or linked list), such a collection would be unlikely to perform as well as one backed by an array. One might argue that a better way to provide for covariant immutable collections would have been to define a "covariant immutable array" type they could use a backing store, but simply allowing array covariance was probably easier.
Arrays will frequently be mutated by code which doesn't know what type of thing is going to be in them, but won't put into the array anything which wasn't read out of that same array. A prime example of this is sorting code. Conceptually it might have been possible for array types to include methods to swap or permute elements (such methods could be equally applicable to any array type), or define an "array manipulator" object which hold a reference to an array and one or more things that had been read from it, and could include methods to store previously-read items into the array from which they had come. If arrays were not covariant, user code would not be able to define such a type, but the runtime could have included some specialized methods.
The fact that arrays are covariant may be viewed as an ugly hack, but in most cases it facilitates the creation of working code.
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)"
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
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.
Related
I'm having trouble figuring out what type parameter is expected at RHS of the following
ArrayList<Pair<ParseNode,ParseNode>>[] nodes = new ArrayList[indexes.length];
Why a copy of <Pair<ParseNode,ParseNode>> is not legitimate?
Arrays of concrete paramaterized types are inherently broken. Remember arrays are covariant and the array type check is a runtime operation. At runtime all the generics have been type erased, so the Array Store check can't tell <Pair<ParseNode, ParseNode>> from <Pair<BigInteger,IOException>>.
The fundamental contract of a generic is "I, the compiler, promise that if you write code that generates no warnings, you will never get a class cast exception at runtime."
Neither can the compiler guarantee to you that it will be able to give you a compile time error if something that is not an ArrayList<Pair<ParseNode,ParseNode>> is put into that array. Nor can the runtime system guarantee you will get an ArrayStoreException (like the Language Specification says it will) if you add the wrong type, rather than a ClassCastException later when you take it back out. (The second part is really why it's actually illegal rather than just a warning, it would result in an array that doesn't obey the language specification.)
So it doesn't let you declare them that way and forces you to acknowledge the 'unsafe' warning. That way it has said "I told you I can't guarantee there will not be any class cast exceptions as a result of using this array, it's on you to make sure you only put the right things in here."
Java not supports generic arrays. Arrays are covariant, generics are not. This means that if class A extends class B then A[] is also B[]. And code
A[] a = new A[10];
B[] b = a;
is legal.
But it is not same for generics. You could not assign Foo<T> to Foo<X> even if T extends X. And so elements of Foo<T>[] can't be guaranteed type safe.
EDIT
Excuse me for just linking out, but I've found Java theory and practice: Generics gotchas article, that explains everything about arrays covariance better than I even would dream.
Don't use an array. Use another ArrayList.
ArrayList<List<Pair<ParseNode,ParseNode>>> listOfLists = new ArrayList<List<Pair<ParseNode,ParseNode>>>();
listOfLists.add(new ArrayList<<Pair<ParseNode,ParseNode>>());
I want to create an array of ArrayLists, similar to that in this thread: How to do an array of hashmaps?. However, Java gives the warning
"Cannot create a generic array of ArrayList<String>"
when I try to do the following
ArrayList[] arrayOfLists = new ArrayList[size];
I have sort of understood the problems and the workarounds provided.
I have my own approach which unfortunately does not fix the problem either.
I tried creating a list of ArrayLists and then used toArray().
ArrayList<ArrayList<String>> listOfLists = new ArrayList<ArrayList<String>>();
ArrayList<String>[] arrayOfLists = (ArrayList<String>[])listOfLists.toArray();
Worked fine, but got the warning :
Type safety: Unchecked cast from Object[] to ArrayList<String>[]
When I tried to check for type safety, using
if(listOfLists.toArray() instanceof ArrayList<String>[])
I get the error:
Cannot perform instanceof check against parameterized type ArrayList<String>[]. Use the form ArrayList<?>[] instead since further generic type information will be erased at runtime
Why cant I use this method? Why does toArray() return Object[] instead of ArrayList<String> since the instance was initialised with theArrayList<String>; type?
Any other workarounds/suggestions on how I can get this done? A 2D array will not work since different lists can vary greatly in size.
The currently accepted answer has a major error in describing Java's generics, so I felt I should answer to make sure there aren't any misconceptions.
Generics in Java are an entirely compile-time feature and for the most part don't exist at runtime due to erasure (you can get the runtime to cough up generic type information in some cases, but that's far from the general case). This provides the basis for the answers to your questions.
Why cant I use this method?
Because generics are erased, an ArrayList<String>[] (as well as all other parameterized ArrayList<>[] instances) at runtime is really an ArrayList[]. Thus, it is impossible for the runtime to check if something is instanceof ArrayList<String>[], as the runtime doesn't actually know that String is your type parameter -- it just sees ArrayList[].
Why does toArray() return Object[] instead of ArrayList since the instance was initialised with theArrayList; type?
Again, erasure. The type parameter is erased to Object, so at runtime what you effectively have is an ArrayList<Object>. Because of this erasure, the runtime doesn't have the information necessary to return an array of the proper type; it only knows that the ArrayList holds Objects, so it returns an Object[]. This is why the toArray(T[]) overload exists -- arrays retain their type information, so an array could be used to provide the requisite type information to return an array of the right type.
Any other workarounds/suggestions on how I can get this done?
As you can see, mixing generic stuff and arrays doesn't work too well, so ideally, you wouldn't mix Lists and arrays together. Therefore, if possible, you should use List<List<String>> or something of the sort instead of List<String>[]. If you want to keep a ArrayList<String>[], though, you could do this:
#SuppressWarnings("unchecked")
ArrayList<String>[] array = new ArrayList[size];
You'll still get the unchecked type warning, but you can be reasonably sure that you won't encounter heap pollution as the only reference to the object is through array. You can also use this as the parameter to toArray():
#SuppressWarnings("unchecked")
ArrayList<String>[] temp = new ArrayList[0];
ArrayList<String>[] arrayOfLists = listOfLists.toArray(temp);
or
#SuppressWarnings("unchecked")
ArrayList<String>[] arrayOfLists = listOfLists.toArray((ArrayList<String>[]) new ArrayList[0]);
For more reading on why you can't parameterize an array, see this SO question. In short, such a thing isn't safe because arrays are covariant, while generics are invariant.
The problem is that Generics are created during runtime, but type conversions and array sizes must be checkable at compile time. The compiler cannot tell what class ArrayList<String> will be during compile time (as it will be generated later), it can only tell that it will be at least an Object, because every class in Java is at least an Object. You can do type conversion and suppress the warning and it might even work, but you run into a pitfall to accidentally confuse types somewhere and mess up your code.
Java is a type-safe language by choice to prevent you from doing one of the most recurring mistakes programmers do in their daily work: confusing variable types. So while it is possible to do the type conversion, you - as an upcoming good Java programmer - should not do that. Use the ArrayList<ArrayList<String>> if you need such a construct, and use arrays only when they are necessary.
The main reason to use arrays is speed of execution, as obviously using an object will keep the runtime busy with some overhead. The main reason to not use arrays is the fact that this overhead will allow you more flexibility in coding and reduce the amount of errors you make. So as a general advice: unless you know (as in measured and determined to be a bottleneck) that you need the speed, go with Lists. Java even does some internal optimizations beyond what you would expect to speed up Lists to a point where they come very close to the execution speed of arrays.
Generally, Java can be considered as a type-safe language. I know that there are some flaws with generics, but I recently came across a Problem I never had before.
To break it down:
Object[] objects = new Integer[10];
objects[0] = "Hello World";
will NOT result in a compile-time error as expected. I would assume that the declaration of an Array of Object will disallow to point to to an array of something else. In Generics I'm not allowed to make such weird things like:
ArrayList<Object> objs = new ArrayList<Integer>
and if I try to kind of trick Java into doing something with
ArrayList<? extends Object> objects = new ArrayList<Integer>
I'm allowed to declare it, but I can only add Objects of type null.
Why doesn't Java prevent the declaration of such weired arrays?
Firstly, I should point out that this is type-safe.
Object[] objects = new Integer[10];
objects[0] = "Hello World";
because an exception will be thrown. (It is not statically type-safe ... but that is a different statement entirely.)
The reason that Java allows this is historical. Until Java 5, Java did not support any form of generics. Gosling has said that if they had had the time to figure out and incorporate generics into Java 1.0, they would have done so.
Unfortunately, they didn't. But they still wanted to be able write things like a general purpose sort method with the following signature:
void sort(Object[] array, Comparator comp) ...
To make this method work for any kind of object array (without generics), it was necessary to make arrays covariant; i.e. to make it legal to pass a String[] or Integer[] as an argument where the formal type is Object[]. If they hadn't done that you would have had to copy the String[] to an Object[], sort it, and then copy it back.
I don't think there's an answer to this besides "legacy design". (Which I admit is a fancy way of saying "because".) You pretty much need to be able to do an equivalent of the last assignment you show somehow. (Otherwise you're stuck to making lots and lots of copies with manual up/down casts, assuming language features of Java pre 1.4)
In Java 1 when type semantics for arrays were basically set in stone, generics weren't available, or even up for consideration for a long while yet. So there was no mechanism available to express the higher-order type constraints needed to make this construct type-safe – and Gosling (IIRC a fan of simplicity) felt resolving this edge case of compile-time type safety wasn't worth complicated the language with whichever solutions were available. Or wasn't bothered by doing the check at runtime enough to even look for a solution. (At the end of the day language design decisions are arbitrary to at least some degree, and there's only one person that could answer this with any certainty.)
"Because it has to".
To elaborate a bit, consider the following example:
Object[] objects = null;
if (something) {
objects = new Integer[10];
} else {
objects = new String[10];
}
Now, how would the Java compiler know which assignments to allow and which to refuse? It can't. The compile-time type is Object so the compiler will let you put any Object in your array, simply because it doesn't have any knowledge of the runtime type of your array.
actually in case of arrays you get a exception at run time called ArrayStoreException when you add wrong type of element In this case a String. In case of generics there is no such exception. its for the very same reason you are not allowed to add anything but object, as you might just add a wrong type into the list.
There's Discussion that I found while I google it
I Found:
Firstly, arrays do not break type safety. If they did, then upcasting
an array wouldn't fail at runtime. They make it impossible for the
compiler to prove the program type-safe, so the checking is deferred
until runtime.
I think confusion occurs here because one the one hand, since a String
is-a Object an array of Strings is-obviously-an array of Objects, and
on the other it clearly isn't. The answer is in the mutability of an
array.
If the array is immutable, then it safe to treat a String[] as an
Object[], because an immutable String[] is always exactly like an
immutable Object[].
On the other hand, if the array is mutable, then it is not usually
safe to treat a String[] as an Object[].
The "wildcards" technique described in the above link is exactly what
CommonLisp has been doing for years.
(deftype StringArray? () (array String)) ; This is the type of arrays of String
(deftype ObjectArray? () (array Object)) ; This is the type of arrays of Object
(subtypep StringArray? ObjectArray?) ; Is StringArray? a subtype of ObjectArray?? false, true ; No, it isn't. (false: it isn't, true: I'm ure)
(deftype AnyArray? () (array *)) ; This is the type of arrays of anything (subtypep StringArray? AnyArray?) ; Is StringArray? a subtype of AnyArray?? true, true ; Yes, it is. (true: it is, true: I'm sure)
Maybe I am overlooking something very easy and obvious...
I have a method interface which goes like
private void render(Collection<Object> rows);
Now, the objects I need to pass is an array (from an enum):
Module[] mods = Module.values();
widget.render(mods);
Of course this does not work, but why does this not work:
widget.render(Arrays.asList(mods))
It turns my array to a collection of Module, and Module is an object...
Try changing your method signature to:
private void render(Collection<?> rows);
This is is saying your method takes a Collection with any type of element, whereas before it was saying the Collection should specifically have Object as its type parameter.
Using a wildcard like this will place limitations on how you can use the Collection that is passed into the method, especially in regard to modifying it. You might want to show us what your render method is doing if you want more detailed advice.
This post is worth reading with respect to using wildcarded collections in Java: What is PECS (Producer Extends Consumer Super)?
Since a Collection<Object> is not a Collection<Module>. A nice tutorial about generics is available in PDF version, and is a must-read when you work with generics.
This specific case if for example explained on page 4, in the Generics and Subtyping part.
If you casted each Object in the Collection to Module, example:
if(object instanceof Module)
{
Module m = (Module)object;
//Do stuff here with m
}
Then it should work as well. Otherwise the other two answers also work just fine.
The reasons for this are based on how Java implements generics. The best way I have found to explain it is by precisely comparing arrays and generic collections.
An Arrays Example
With arrays you can do this:
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
But, what would happen if you try to do this?
Number[0] = 3.14; //attempt of heap pollution
This last line would compile just fine, but if you run this code, you could get an ArrayStoreException.
This means that you can fool the compiler, but you cannot fool the runtime type system. And this is so because arrays are what we call reifiable types. This means that at runtime Java knows that this array was actually instantiated as an array of integers which simply happens to be accessed through a reference of type Number[].
So, as you can see, one thing is the real type of the object, an another thing is the type of the reference that you use to access it, right?
The Problem with Java Generics
Now, the problem with Java generic types is that the type information is discarded by the compiler and it is not available at run time. This process is called type erasure. There are good reason for implementing generics like this in Java, but that's a long story, and it has to do with binary compatibility with pre-existing code.
But the important point here is that since, at runtime there is no type information, there is no way to ensure that we are no committing heap pollution.
For instance,
List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts;
myNums.add(3.14); //heap polution
If the Java compiler does not stop you from doing this at compile time, the runtime type system cannot stop you either, because there is no way, at runtime, to determine that this list was supposed to be a list of integers only. The Java runtime would let you put whatever you want into this list, when it should only contain integers, because when it was created, it was declared as a list of integers.
As such, the designers of Java made sure that you cannot fool the compiler. If you cannot fool the compiler (as we can do with arrays) you cannot fool the runtime type system either.
As such, we say that generic types are non-reifiable.
Evidently, this would hamper pollymorphism as well pointed out. The solution is to learn to use two powerful features of Java generics known as covariance and contravariance.
Covariance
With covariance you can read items from a structure, but you cannot write anything into it. All these are valid declarations.
List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>()
List<? extends Number> myNums = new ArrayList<Double>()
And you can read from myNums:
Number n = myNums.get(0);
Because you can be sure that whatever the actual list contains, it can be upcasted to a Number (after all anything that extends Number is a Number, right?)
However, you are not allowed to put anything into a covariant structure.
myNumst.add(45L);
This would not be allowed, because Java cannot guarantee what is the actual type of the real object. It can be anything that extends Number, but the compiler cannot be sure. So you can read, but not write.
Contravariance
With contravariance you can do the opposite. You can put things into a generic structure, but you cannot read out from it.
List<Object> myObjs = new List<Object();
myObjs.add("Luke");
myObjs.add("Obi-wan");
List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);
In this case, the actual nature of the object is a List of Objects, and through contravariance, you can put Numbers into it, basically because numbers have Object as the common ancestor. As such, all Numbers are objects, and therefore this is valid.
However, you cannot safely read anything from this contravariant structure assuming that you will get a number.
Number myNum = myNums.get(0); //compiler-error
As you can see, if the compiler allowed you to write this line, you would get a ClassCastException at runtime.
Get/Put Principle
As such, use covariance when you only intend to take generic values out of a structure, use contravariance when you only intend to put generic values into a structure and use the exact generic type when you intend to do both.
The best example I have is the following that copies any kind of numbers from one list into another list.
public static void copy(List<? extends Number> source, List<? super Number> destiny) {
for(Number number : source) {
destiny.add(number);
}
}
Thanks to the powers of covariance and contravariance this works for a case like this:
List<Integer> myInts = asList(1,2,3,4);
List<Integer> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();
copy(myInts, myObjs);
copy(myDoubles, myObjs);
I am playing with Generic and arrays, it seems the following code compiles fine,
ArrayList<Key> a = new ArrayList<Key>();
But the compiler complains about this one,
ArrayList<Key>[] a = new ArrayList<Key>[10];
By reading post in stackoverflow, I sort of understand that this is due to Type Erasure and I can fix it by using,
ArrayList<Key>[] a = (ArrayList<Key> []) new ArrayList[10];
or list of list
ArrayList<ArrayList<Key>> b = new ArrayList<ArrayList<Key>>();
But I can't figure out the reason behind the scene. Especially, why the second one is illegal given the first one is perfectly OK. And why the compiler does not complain about the list of list.
You can't have an array, because an array requires a raw type. You typecast it in the second instance, which makes it fit the defined type, and is therefore legal (however, this is impossible for it to infer). The list of list is legal as ArrayList isn't an array.
Read chapter 7.3 (page 15) in the official tutorial for more details on this.
The component type of an array object may not be a type variable or a
parameterized type, unless it is an (unbounded) wildcard type.You can
declare array types whose element type is a type variable or a
parameterized type, but not array objects.
This is annoying, to be sure. This restriction is necessary to avoid situations like:
List<String>[] lsa = new List<String>[10]; // not really allowed
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // unsound, but passes run time store check
String s = lsa[1].get(0); // run-time error - ClassCastException
If arrays of parameterized type were allowed, the example above would
compile without any unchecked warnings, and yet fail at run-time.
The tutorial then goes on to say the following:
Since type variables don’t exist at run time, there is no way to determine what the
actual array type would be.
The way to work around these kinds of limitations is to use class literals as run time
type tokens
Array was poor man's generics; with real generics, one should avoid arrays, though not always possible.
Arrays are covariant, generics are invariant; combined with erasure, things just don't fit very well, as illustrated by the example in Chris's answer.
However I think it is possible to relax the spec to allow generic array creation - there's really no problem there. The danger comes when up casting the array; a compiler warning at that point is enough.
Actually Java does create generic arrays for vararg methods, so it's a little hypocritical.
Here are utility methods taking advantage of that fact
#SafeVarargs
static <E> E[] arrayLiteral(E... array)
{
return array;
}
#SafeVarargs
static <E> E[] newArray(int length, E... array)
{
return Arrays.copyOf(array, length);
}
// usage
List<String>[] array1 = arrayLiteral(list, list);
List<String>[] array2 = newArray(10);
I had a similar question myself - FWIW, I didn't find the answers persuasive. The pertinent section from the most detailed answer (referring to the pdf reference) is this:
The component type of an array object may not be a type variable or a
parameterized type, unless it is an (unbounded) wildcard type.You can
declare array types whose element type is a type variable or a
parameterized type, but not array objects. This is annoying, to be
sure. This restriction is necessary to avoid situations like
List<String>[] lsa = new List<String>[10]; // not really allowed
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // unsound, but passes run time store check
String s = lsa[1].get(0); // run-time error - ClassCastException
So because I can cat the List[] to Object[], then shove something incorrect into the Object[], then refer to incorrectly from the List reference, through the casted ref, this is bad/disallowed? But only with new?
It's still more than a bit obscure to me how declaring this with new is any more or less of a problem than the usage, still crossing my eyes staring at it in the hope that it will start to make sense, or at least resolve into a nice 3d image.
Creating generic arrays isn't type-safe (see "Item 25: Prefer lists to arrays" of "Effective Java - second edition" by Joshua Bloch).
Use:
List<List<Key>> b = new ArrayList<List<Key>>(10);
Or with Java SE 7:
List<List<Key>> b = new ArrayList<>(10);
The arrays allow to escape type checks (as illustrated in the Chris's answer). So, you could have a code which passes all compiler checks (no "unchecked" warnings from compiler), but fail at run time with ClassCastException.
Forbidding this construction raises the problem for a developer, so warnings do appear.