Reputation: 92180
While answering to a question about that here: https://stackoverflow.com/a/9872630/82609
I tried to do the following:
Comparator<String>[] comparators = new Comparator[] {...};
It works! But the following doesn't:
Comparator<String>[] comparators = new Comparator<String>[] {...};
On the related question, i made the assumption:
I guess it's because initially the array contract may be something like this:
If you create an array of type X, you will NEVER EVER be able to put anything in it that IS-NOT-AN X. If you try, you'll get an ArrayStoreException
Thus allowing arrays with generics creation would lead to a different rule like:
If you create an array of type
X<Y>
, you will NEVER EVER be able to put anything that IS-NOT-AN X. If you try, you'll get an ArrayStoreException. But you CAN add bothX<Y>
andX<Z>
objects because of type erasure!
But thinking about it, would it really be a problem to have:
Comparator<String>[] comparators = new Comparator<String>[] {...};
I don't really understand why it's not possible, since using such a thing would:
Finally we can use an array with generic type reference and because of the impossibility to create an array with a generic type, i think many people do not even know it's possible.
I just wonder if someone knows the reason behind this choice?
It's a bit like forcing people to use List<String> = new ArrayList();
instead of using List<String> = new ArrayList<String>();
dimitrisli you gave a nice exemple from Joshua Bloch's famous book. As you/he explained it, it is dangerous to use both generic arrays + covariance and could lead to ClassCastException while we expect ArrayStoreException from an array with use of covariance.
But please notice the following is still legal and lead to the same:
List<String>[] stringLists = new List[1];
List<Integer> intList = Arrays.asList(42);
Object[] objects = stringLists;
objects[0] = intList;
String s = stringLists[0].get(0);
However it produces an unchecked cast warning at compile time, and as you mentionned, a ClassCastException at runtime.
Upvotes: 6
Views: 602
Reputation: 21401
Quoting from the great Effective Java Second Edition page 120:
Why generic array creation is illegal - won't compile!
List<String>[] stringLists = new List<String>[1]; // (1)
List<Integer> intList = Arrays.asList(42); // (2)
Object[] objects = stringLists; // (3)
objects[0] = intList; // (4)
String s = stringLists[0].get(0); // (5)
Let’s pretend that line 1, which creates a generic array, is legal. Line 2 creates and
initializes a List<Integer>
containing a single element. Line 3 stores the
List<String>
array into an Object array variable, which is legal because arrays
are covariant. Line 4 stores the List<Integer>
into the sole element of the
Object array, which succeeds because generics are implemented by erasure: the
runtime type of a List<Integer>
instance is simply List, and the runtime type of
a List<String>[]
instance is List[]
, so this assignment doesn’t generate an
ArrayStoreException
. Now we’re in trouble. We’ve stored a List<Integer>
instance into an array that is declared to hold only List<String>
instances. In
line 5, we retrieve the sole element from the sole list in this array. The compiler
automatically casts the retrieved element to String, but it’s an Integer, so we get
a ClassCastException
at runtime. In order to prevent this from happening, line 1
(which creates a generic array) generates a compile-time error.
Upvotes: 1
Reputation: 103817
I see where you're coming from (and in a practical sense I basically agree), but I think there is a difference that motivates the current situation.
As you mention, erasure means that generic parameters aren't available at runtime and so the types are checked at compile time (be that for a List<String>
or your Comparator<String>[]
). Critically, this is based on the generic parameter of the variable.
Arrays on the other hand check the types of their argument at runtime, when they're inserted, so they can throw an ArrayStoreException
if they're misused (typically due to abusing their covariance). Arrays therefore need to be able to perform both of your bullet point checks internally, and of course they can't check the generic parameter at runtime. Hence it makes no sense to instantiate a generic array, since the array would have to completely ignore the generic parameter, which would be misleading at best.
That said, it does make sense to assign such an array to a parameterised reference, since then the compiler can perform generic checks. And you're right in thinking that this covers all the bases and ensures that the generic types are checked (so long as the variables are parameterised correctly).
The bottom-line reason behind this choice, and why arrays are different to collections in this respect, is that arrays are required to actually check the types of their arguments when they're inserted, whereas collections merely take your word for it and allow type errors to bubble through into a ClassCastException
later.
Upvotes: 4