Why can we use array with generic reference
Asked Answered
A

2

6

While answering to a question about that here: https://mcmap.net/q/1780157/-store-comparators-objects-in-array

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 both X<Y> and X<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:

  • Check the classes inserted at runtime
  • Check the classes type inserted at compile time

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.

Auriscope answered 27/3, 2012 at 9:7 Comment(6)
possible duplicate of Error generic array creationCampion
Someone has already asked the same question. #3903696Campion
@DaveWebb I don't think that's quite the same question - the linked question is asking about the compile error and how to work around it, while this is asking about the motivating philosophy behind the language design.Jointed
Read the first answer. There is a link in there to a detailed explanation of what is happening here.Campion
this link has some answers for your question ibm.com/developerworks/java/library/j-jtp01255/…Faery
@DaveWebb i agree, the link you talk about has a section about that. But i still think the questions are different. It's like "why can't i do List<Animal> aList = new ArrayList<Dog>()" VS "How generics really work"Auriscope
J
4

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.

Jointed answered 27/3, 2012 at 9:24 Comment(1)
Thanks, i think you pointed it out: the problem is probably about array covarianceAuriscope
H
1

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.

Hangout answered 27/3, 2012 at 9:26 Comment(2)
Thanks, nice exemple, i edited my question. Notice that we can still use List<String>[] stringLists = new List[1]; instead and it works fine. Do you know why it's allowed?Auriscope
This is a type safety warning because we are trying to create by using the raw signature List[] and assign it to the generic List<String>[]. The moment we change it though to List<String>[] the compiler complains in order to avoid the described situation.Hangout

© 2022 - 2024 — McMap. All rights reserved.