Parameterized type are NOT inherently unsafe
Time to time, i heard that sentence
"array of generics a inherently unsafe"
or a variation.
That is not true and i would like to explain why
and even better propose a way to improve the
user experience.
Array of generics ?
First, there are two kinds of "generics", type variable and parameterized type, and they behave differently.
An example of array of type variable:
class A<T> { // T is a type variable
T[] m() { // T[] is an array of type variable
...
}
}
...
A<String> a =new A<String>();
Here, T is a type variable. A>String> is a parameterized type, it's an instantiation of A<T> with T = String.
As Gilad Bracha in the
generics tutorial
wrote
"The component type of an array object may not be a type variable or a parameterized type, unless it is an (unbounded) wildcard type",
currently the specification doesn't allow to create
an array of type varaible
and array of parameterized type.
Why ?
Let's take the example given by Gilad:
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; // problem here String s = lsa[1].get(0);
Generics in Java are not reified, i.e. at runtime there
is no difference between a List<String> and
a List<Integer>, they are all List.
So the VM can perform runtime check to distinguish
between the two parameterized type.
Just before the bytecode generation,
in a pass named
erasure,
the compiler transform all
parameterized types to types without type arguments
and replace type variables by their first bound.
So our example is erased to:
List[] lsa = new List[10]; // not really allowed Object o = lsa; Object[] oa = (Object[]) o; List li = new ArrayList(); li.add(new Integer(3)); oa[1] = li; // problem but not detected here String s = lsa[1].get(0); // run-time error - ClassCastException
So when VM execute the line oa[1] = li; it doesn't detect a problem. The VM generate a ClassCastException later here at the next line.
To avoid to have a code that can generate a CCE without any warning, the JSR15 experts decide that array of parameterized type can not be created.
Here comes the pain
Because you often need to create an array of parametrized type,
you have to use painful workaround.
In a recent interview about Effective Java, Joshua Bloch
talk about
"The "impedance mismatch" between arrays and generics can be a pain."
An example of such tricks, the creation of an array of parametrized type is replaced by the creation of an array of wildcard then a cast.
List<String>[] list =
(List<String>[])new ArrayList<?>[1]; // warning
These workaround codes are very dangerous because if one day in the future generics are reified (JDK8 :), these codes will not work anymore and throw a ClassCastException.
So what ?
So array creation is prohibited because it can lead
to unsafe code. I think it's time to say:
Sorry we goof, we can do better.
Instead of preventing to create array of parametrized type
we can just generate a warning where needed.
What is the problem in our running example ?
The line oa[1] = li; is unsafe because
a List of Integer is store in an Object.
In general terms, the problem is that when converting an array of parameterized type to a type without type argument, the type is lost (by definition) so neither the compiler nor the VM (because of the erasure) can garantee the type safety.
Unsafe convertion
Using a JLS like wording,
let T and U such as U is the declared type of u and T is
defined by, T t=u;
If U is an array of parametrized type,
the compiler may emit a warning, if :
- T is Object, Cloneable, Serializable or
- T is an array of V which is not parametrized or
- T is an array of raw type or
- T is an array of unbunded wilcard.
If this rule is added, creation of array of parameterized type is safe and then can be allowed.
And what about the backward compatibility ?
With this new rule, program that compile will continue to
compile. Workarounds are not needed any more.
But some code that currently don't raise a warning
can now raise a warning.
There are in my opinion two different cases:
SelectionKey key = ... List<String> list = ... key.attach(list); // will generate a new warning
It's not a big deal because the following code already generate a warning.
List<String> list =
(List<String>)key.attachment(); // already generate a warning
The second case is more problematic
List<String> list = ... System.out.println(list); // warning
Here, println take an Object, so the parameterized list
is assigned to an Object.
Hum, such warning is less desirable.
Here, the compiler doesn't know that the only things
done to that object is to call toString() on it
so it's safe.
To avoid such warning, i propose to add a new key to
@SuppressWarnings, @SuppressWarnings("parameterized-type")
in order to indicate that assign/convert a parameterized type
is safe in that case.
public void println(@SuppressWarnings("parameterized-type") Object x) {
I wish and hope, these changes can be included in JDK7.
Is array of type variable inherently unsafe ?
Yes, array of type variable are inherently unsafe. See the following code.
class A<T> {
T[] m() {
return new T[1]; // erasure create a, array of Object
}
}
...
A<String> a = new A<String>();
String[] s = a.m(); // oups
cheers,
Rémi
- Login or register to post comments
- Printer-friendly version
- forax's blog
- 1460 reads






Comments
by forax - 2008-05-29 04:03
Howard, oa[ 1 ] = upCast( li ); is infered TheClass.<Object,List<Integer>>upCast(li); so the compiler should generate a warning here because you try to convert a parameterized type to an Object.
Rémi
by hlovatt - 2008-05-29 03:38
I think Neal is right:
static <T, S extends T> T upCast( final S s ) { return s; // this needs to generate a warning for the new proposal } static void neal() { @SuppressWarnings( "unchecked") final List<String>[] lsa = new List[ 10 ]; final Object[] oa = (Object[]) lsa; final List<Integer> li = new ArrayList<Integer>(); li.add( new Integer( 3 ) ); oa[ 1 ] = upCast( li ); // no problem now, types correct final String s = lsa[ 1 ].get( 0 ); // runtime error generated here System.out.println( s ); }by forax - 2008-05-29 01:55
Howard, reified generics is a must have and it's clear that's no average duke understands variance. About compatibility, in my opinion javac should now warns on every raw types explicitly saying that it will not work in 1.8.
Neal, i don't think you need a warning if the type variable is not seen as an Object. The type variable capture the type so there is no need to generate a warning.
By example, the following code is safe: <T> T m(T t) { return t: // ok safe } but not this one: <T> Object m(T t) { return t; // warning here } Rémi
by hlovatt - 2008-05-28 01:16
Wouldn't it be easier to stop erasing the types and whilst we are at it get rid of variance. (http://www.artima.com/weblogs/viewpost.jsp?thread=222021). Yes, it will break old code. But most things are generic these days, so they can be recompiled. We don't need to interact with raw types like we used too. I think it would now be worth the pain.by gafter - 2008-05-27 23:27
Variable initialization is not the only place the warning would be required... everywhere the subtype relationship makes something legal, such a warning would be required. For example, using one of these "U" types as a type parameter must cause a warning (it is allowed because it is a subtype of the type parameter's bound, which by default is Object). If you apply this to its logical conclusion you'll find you can hardly do anything with these types without incurring the wrath of the compiler, and the causes would be far more difficut to understand than the status quo.by hlovatt - 2008-05-29 23:10
@Rémi, this is probably a dumb question so please bare with me. How would your proposal deal with this:
@SuppressWarnings( "unchecked" ) final List<Double>[] lda = new List[ 1 ]; final List<? extends Number>[] lna = (List<? extends Number>[]) lda; final List<Integer> li = Arrays.asList( 3 ); lna[ 0 ] = upCast( li ); // no problem now, types correct final Double d = lda[ 0 ].get( 0 ); // runtime error generated here