Generics have been available in Java since J2SE 5.0 was released in 2004. Generics allow for parameterised types — for example, an instance of the Vector class can be declared to be a vector of strings (written Vector<String>) — and hence allow the compiler to enforce type safety. To avoid major changes to the Java run-time environment, generics are implemented using a technique called type erasure. This is equivalent to removing the additional type information and adding casts where required.
Java’s arrays have always allowed parameterised types, but written in a different form: the array type String[] is analogous to the vector type Vector<String>. Arrays are not subject to type erasure, and this article details the problems caused by the inconsistencies in the handling of arrays and generic types.
Casting with arrays
Consider the following code using arrays:
1 2 3 4 5 6 7 8 |
|
Java allows this to compile, despite the fact that casting a array of strings to an array of objects array introduces the possibility of run-time errors. Line 8 demonstrates this, causing a run-time exception of a type created specifically for this situation: java.lang.ArrayStoreException: java.lang.Object. Java’s developers deliberately chose to allow this behaviour — people intuitively expect the cast to work as, for example, in real life a list of cars (the sub-type) is a list of vehicles (the super-type).
Casting with generic types
Now consider similar code using vectors:
1 2 3 4 5 6 7 8 |
|
Java does not allow this to compile — the second line causes an ‘incompatible types’ error. According to Sun Microsystem’s document Generics in the Java Programming Language, this implementation was chosen to avoid run-time exceptions. However, as we have seen above, the Java developers did not see this as a problem when allowing casts of this form for arrays. In reality, this behaviour is forced upon the developers by the choice to use type erasure — line 8 cannot cause a run-time exception (as the equivalent line did in the array example) because the parameterised types do not exist at run-time, and hence the variable created in line 2 just has type Vector.
Generic arrays
In effect, arrays behave like generic types without type erasure. One consequence of this is that the following code (based on an example of illegal code from Generics in the Java Programming Language) won’t compile:
1 2 3 4 5 6 7 8 9 |
|
This causes a ‘generic array creation’ error. As arrays don’t support type erasure, but the parameterised type T does not exist at run-time, the compiler cannot assign a run-time type to the array created.
Making Class generic
To solve this problem, the Class class was converted to be generic. Consider the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
As long as tClass initialised to an instance of the appropriate class (for example, in the constuctor), the method can now return an array of the correct type. This technique of using java.lang.reflect.Array is used by the toArray() methods of classes in the Collections Framework.
Note that there is a cast to the type T[]. This is required by the signature of the newInstance method:
1 2 3 |
|
This cast causes an unchecked exception. Java allows programs to compile with this unchecked exception, introducing the possibility of run-time errors, because situations such as that shown above rely on this behaviour. This leads to further inconsistencies in the behaviour of arrays and generic classes.
Casting to sub-types
Consider the following code:
1 2 3 4 5 |
|
Java allows this to compile, but line 5 causes a run-time exception, ‘java.lang.ClassCastException: [Ljava.lang.Object’, even if all the objects in the Object array are of run-time type String. This differs from the behaviour of casts to super-types, where casts are assumed to be safe and run-time exceptions are caused when the array is accessed later.
Type erasure and generic classes
Consider the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Java allows this to compile. Line 5 causes an unchecked exception, and does not cause a run-time exception. Further problems are caused by type erasure — because the object has run-time type Vector, the line 8 does not cause a run-time exception either. Although line 11 accesses the object in the vector through the ‘strings’ variable, a ClassCastException is not thrown as the Java compiler removes the implicit cast to type String for efficiency. Line 14 finally causes an exception, as the object returned cannot be cast to a string.