As has been pointed out in several posts in this forum, declaration of generic arrays is not allowed (with the exception of wildcard arrays). I don't understand why this restriction is enforced, because this unsoundness is
not due to the use of arrays, but rather the use of Generics at all.
To illustrate this point, consider Bracha's example from "Generics in the Java Programming Language":
List<String>[] lsa = new List<String>[10]; // not really allowed
Object o = lsa;
Object[] oa = (Object[])o;
oa[1] = new ArrayList<Integer>(); // unsound, but passes run time store check
String s = lsa[1].get(); // run-time error - ClassCastException
I modify this example slightly by actually inserting an Integer into the ArrayList<Integer> and adding line numbers. I believe this still illustrates the same point:
List<String>[] lsa = new List<String>[10]; // line 1
Object o = lsa; // line 2
Object[] oa = (Object[])o; // line 3
ArrayList<Integer> ali = new ArrayList<Integer>(); // line 4
ali.add(new Integer(0)); // line 5
oa[1] = ali; // line 6
String s = lsa[1].get(0); // line 7
Now, I have rewritten the example to use an ArrayList<List<String>> instead of an array of List<String>:
ArrayList<List<String>> lsa = new ArrayList<List<String>>(10); // Equivalent to line 1.
for (int i = 0; i < 10; i++) // When an array is created, all its
{ // elements are initialized to their
lsa.add(null); // default value. For Objects, this
} // is null.
Object o = lsa; // Equivalent to line 2.
ArrayList oa = (ArrayList)o; // Equivalent to line 3
ArrayList<Integer> ali = new ArrayList<Integer>(); // Equivalent to line 4.
ali.add(new Integer(0)); // Equivalent to line 5
oa.set(1,ali); // Equivalent to line 6
String s = lsa.get(1).get(0); // Equivalent to line 7. (run-time error - ClassCastException)
This code compiles (with warnings) and runs, resulting in the expected ClassCastException. I suggest Bracha's example with arrays is no more unsound than my example. Despite this fact, the two cases are handled differently. The compiler generates an error for the case with arrays and only a warning in the case without arrays.
Can anyone explain to me the reasoning behind this decision?
this unsoundness is not due to the use of arrays, but rather the use of
Generics at all.
It has nothing to do with "soundness".
When you write
new T[size];
in a generic routine, the compiler has no idea which constructor to generate a call to at runtime. The actual information about the actual class that matches "T" is not passed to the generic routine (all generic type information is erased before runtime), so there is no class handle available for the generated code.
This is just an unfortunate consequence of generic type parameter erasure.
Re: Generic Arrays - No more unsound than Generics in general
Oct 19, 2004 6:42 PM
(reply 2
of 27) (In reply to
#1 )
I'm sorry if I wasn't clear, but I'm trying to get at a different issue here.
I'm not concerned about the case where the array type itself is a type parameter (i.e. T[]).
The issue highlighted by my example refers to an array that takes a type parameter (i.e. List<String>[]).
There is nothing ambigous about the type of the array; it is an array of List, and the compiler knows exactly what constructor to call. The problem I'm interested in rests in the way compile time type checking is done (or not done, as the case may be) with arrays that take a type parameter.
Re: Generic Arrays - No more unsound than Generics in general
Oct 19, 2004 7:27 PM
(reply 3
of 27) (In reply to
#1 )
this unsoundness is not due to the use of arrays,
but rather the use of
Generics at all.
It has nothing to do with "soundness".
When you write
new T[size];
in a generic routine, the compiler has no idea which
constructor to generate a call to at runtime. The
actual information about the actual class that matches
"T" is not passed to the generic routine (all generic
type information is erased before runtime), so there
is no class handle available for the generated code.
This is just an unfortunate consequence of generic
type parameter erasure.
Taken from ArrayList's source, loosely paraphrased from memory
class ArrayList<T> extends...
{
private T[] elements;
...
//inside default constructor
{
elements = (T[]) new Object[10];
}
...
}
Re: Generic Arrays - No more unsound than Generics in general
Oct 20, 2004 8:30 AM
(reply 4
of 27) (In reply to
#3 )
I still do not see how this answers my original question, which is why ArrayList<List<String>> should be allowed by the compiler while List<String>[] should not.
My question is not about why the compiler behaves the way that it does. I want to know why the decision was made to implement it that way. I want to know why changing the compiler to allow declaration of generic arrays would introduce any more type unsafety than the current syntactic structures allowed by the compiler.
Re: Generic Arrays - No more unsound than Generics in general
Oct 21, 2004 8:20 AM
(reply 5
of 27) (In reply to
#4 )
My question is not about why the compiler behaves the
way that it does. I want to know why the decision
was made to implement it that way. I want to know why
changing the compiler to allow declaration of generic
arrays would introduce any more type unsafety than the
current syntactic structures allowed by the compiler.
Nathan, I think you've actually asked an interesting question and you've very much been getting stock answers from people who didn't take the time to read you question closely.
I'm certainly not Neal or Gilad, but after thinking a little about the example, here's my take on why it might be disallowed:
List<String>[] lsa = new ArrayList<String>[10]; //this is not allowed, of course
List [] la = lsa;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
la[1] = li;
String s = lsa[1].get(0);
This is Gilhad's first example rewritten to be a little more obscure. His code is trying to illustrate a point, so its spelled out extremely explicitly what he's doing. He uses two lines:
Object o = lsa;
Object[] oa = (Object[]) o;
Whereas I've done something less explicit but more "realistic" in one line:
List [] la = lsa;
If you read the next two examples in section 7.3 of "Generics in the Java Programming Language" you'll see that the point he's making is all of the code that he shows that compiles with the 1.5 compiler but throws a ClassCastException at runtime obeys two properties:
there's an explicit cast in the code, indicating something unsafe might be happening (2nd example in 7.3) the compiler emits a warning, indicating something unsafe is probably happening (3rd example in 7.3)
If you look at my example above, you'll see that if it were allowed it would lead to a runtime error with no explicit casts in the code and no compiler warnings.
Your ArrayList based example does generate a warning, at this line:
oa.set(1,ali); // Equivalent to line 6
This, I believe, is the answer to your question. Code with no explicit casts and no warnings generated is supposed to be safe (if its isn't I believe its considered to be a bug). If generic arrays were allowed I could write code that has no casts, generates no warnings but is unsafe. With ArrayLists, I don't think you can avoid the warning.
I'm in two minds about this. It sure would be nice if code with no explicit casts and no warnings is definitely typesafe - but I'm not sure I believe its practical to write code that obeys those rules with the current generics implementation and achieves desirable things like typesafe collections. Things like Class.cast() seem to be necessary here and there, if you want to avoid warnings - but using that to hide a warning is a kind of a cheat.
Of course, perhaps someone can prove me wrong by writing a List based example that is not a bug in the compiler, has no casts and generates no warnings and is unsafe?
I'm certainly not claiming this is the only answer to Nathan's question.
Re: Generic Arrays - No more unsound than Generics in general
Oct 21, 2004 9:08 AM
(reply 6
of 27) (In reply to
#5 )
I'm in two minds about this. It sure would be nice if
code with no explicit casts and no warnings is
definitely typesafe - but I'm not sure I believe its
practical to write code that obeys those rules with
the current generics implementation and achieves
desirable things like typesafe collections. Things
like Class.cast() seem to be necessary here and there,
if you want to avoid warnings - but using that to hide
a warning is a kind of a cheat.
Of course, perhaps someone can prove me wrong by
writing a List based example that is not a bug in the
compiler, has no casts and generates no warnings and
is unsafe?
But as we know, it has always been possible to write code that uses no casts and causes no warnings but still throws a ClassCastException (thinly disguised as an ArrayStoreException) at runtime.
Allowing the the type hierarchy to affect array assignability was a deliberate decision of the original Java language designers. (C++, for example, does not allow this. You cannot assign a Derived** to a Base**).
So back to the original question: Would allowing arrays of generic types create more possibilities for these kinds of RuntimeExceptions?
Re: Generic Arrays - No more unsound than Generics in general
Oct 22, 2004 1:33 PM
(reply 7
of 27) (In reply to
#5 )
Thank you for taking the time to write a well-thought out reply.
This, I believe, is the answer to your question. Code
with no explicit casts and no warnings generated is
supposed to be safe (if its isn't I believe its
considered to be a bug). If generic arrays were
allowed I could write code that has no casts,
generates no warnings but is unsafe. With ArrayLists,
I don't think you can avoid the warning.
I agree, and after staring at your example and Bracha's example for a long time, I finally understand. It took looking at a similar example that does not use arrays for me to realize the problem that the use of arrays introduces:
List<String> ls = new List<String>(); // line 1
Object o = ls; // line 2
List<Integer> li = new List<Integer>(); // line 3
li.add(new Integer(0)); // line 4
o = li; // line 5
String s = ls.get(0); // line 6
The above compiles with no warnings. But what have I really done in line 5? I haven't changed the object that ls points to; I've simply changed o to point to a new, different Object, li. The code will in fact throw a runtime exception because I'm trying to get something from an empty List in line 6, since I haven't actually inserted anything into ls.
In the array version, the List<String>[] pointer lsa and the Object[] pointer both refer to the same object. When oa[1] gets set to an ArrayList<Integer>, the object that lsa points to is modified. We end up with a List<String> pointing to a List<Integer>. This doesn't cause an ArrayStoreException because both objects are Lists, and we have no generic info at runtime.
For the same reasons the compiler does not generate a warning when assigning an Object pointer to refer to a generic object, assigning a location in an Object[] to point to a generic object can't generate a warning.
In summary, I'm convinced of the statement that "if your entire application has been compiled without unchecked warnings using javac -source 1.5, it is type safe." I see why using arrays causes a vulnerability that is not present without arrays, by allowing the target of a pointer to change to a different generic "type" (but same runtime type) without the compiler generating a warning. I also see that the compiler can not be reasonably modified to produce such warnings.
It is unfortunate, but at least I now understand the reasoning behind this.
Re: Generic Arrays - No more unsound than Generics in general
Oct 25, 2004 7:56 AM
(reply 8
of 27) (In reply to
#7 )
List<String> ls = new List<String>(); // line 1
Object o = ls; // line 2
List<Integer> li = new List<Integer>(); // line 3
li.add(new Integer(0)); // line 4
o = li; // line 5
String s = ls.get(0); // line 6
The above is not the List equivalent of the original example. That would be:
List<List<String>> lsa = new List<List<String>>();
Object o = lsa;
List oa = (List)o; // should warn
oa.add(new ArrayList<Integer>());
String s = lsa.get(0).get(0); // run-time error - ClassCastException
Re: Generic Arrays - No more unsound than Generics in general
Oct 25, 2004 8:07 AM
(reply 9
of 27) (In reply to
#8 )
The above is not the List equivalent of the original
example.
You are correct. However, that is not what I was attempting to show with my example. I was trying to show the difference between a stand-alone pointer to a List<String> vs. a pointer to an array location that holds a List<String>. As I explained in my most recent post, I realized it is this difference that causes the additional vulnerability of generic arrays.
Re: Generic Arrays - No more unsound than Generics in general
Oct 25, 2004 9:05 AM
(reply 10
of 27) (In reply to
#9 )
You are correct. However, that is not what I was
attempting to show with my example. I was trying to
show the difference between a stand-alone pointer to a
List<String> vs. a pointer to an array location that
holds a List<String>. As I explained in my most
recent post, I realized it is this difference that
causes the additional vulnerability of generic arrays.
The problem with arrays is not because of the new langauge features it is a reasult of how arrays work in all versions of Java.
Re: Generic Arrays - No more unsound than Generics in general
Oct 25, 2004 9:15 AM
(reply 11
of 27) (In reply to
#9 )
I was trying to
show the difference between a stand-alone pointer to a
List<String> vs. a pointer to an array location that
holds a List<String>. As I explained in my most
recent post, I realized it is this difference that
causes the additional vulnerability of generic arrays.
The example I just gave shows that it has nothing to do with pointers but rather the rules of casting for arrays as opposed to generics.
Test.java:14: warning: [unchecked] unchecked call to set(int,E) as a member of the raw type java.util.ArrayList
oa.set(1,ali); // Equivalent to line 6
^
1 warning