Arguments both for and against type erasure exist, and this document does not promote either view. Instead it explains how Java’s implementation of type erasure (in particular the fact that arrays behave like generic classes without type erasure) causes inconsistencies in behaviour, and leads to the failure of code that seems reasonable.
Consider the following code using arrays:
String[] s=new String[10];
Object[] o=s;
o[0]=new Object();
Java allows this to compile, depsite the fact that casting a String array to an Object array introduces the possibility of run-time errors.
The third line demonstrates this, causing a run-time exception of a type created specifically for this situation: java.lang.ArrayStoreException: java.lang.Object.
Java’s developers chose to allow this behaviour — people intuitively expect the cast to work, as, for example, in real life a list of cars is a list of vehicles.
Now consider similar code using a generic class (in this case java.util.Vector):
Vector<String> s=new Vector<String>(10);
Vector<Object> o=(Vector<Object>)s;
o.add(new Object());
Java does not allow this to compile — the second line causes an ‘incompatible types’ message. Note that this shows that the type variable is treated as part of the type by the compiler. According to Sun Microsystem’s document Generics in the Java Programming Language, this implementation was chosen to avoid run-time exceptions. 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 — the third line cannot cause a run-time exception (as the equivalent line did in the array example) because the types do not exist at run-time.
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:
public class GenericArrayTest<T>{
public <T> T[] returnArray(){
return new T[10];
}
}
This causes a ‘generic array creation’ message from the compiler. As arrays don’t support type erasure, the compiler cannot assign a run-time type to the array created.
To solve this problem, the Class class was converted to be generic.
Consider the following code:
public class GenericArrayTest<T>{
private Class<T> tClass;
// Other code, included code to initialise tClass.
public <T> T[] returnArray() {
return (T[])java.lang.reflect.Array.newInstance(tClass,10);
}
}
As long as tClass initialised to the appropriate class (in the constuctor, for example), 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 T[].
This is required by the signature of the newInstance method:
public static Object newInstance(Class<?> componentType,
int length)
throws NegativeArraySizeException
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.
Consider the following code:
Object[] o=new Object[10];
String[] s=(String[])o;
Java allows this to compile, but the second line 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 supertypes, where casts are assumed to be safe, and run-time exceptions are caused when the array is accessed later.
The behaviour of generic classes is even more permissive. Consider the following code:
Vector<String> s=new Vector<String>(10);
Vector o=s;
o.add(new Object());
Object p=s.get(0);
String t=s.get(0);
Java allows this to compile.
The second line 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 third line does not cause a run-time exception either.
Although the fourth line accesses the Vector through the s variable, a ClassCastException is not thrown as the Java compiler removes the implicit cast to String for efficiency.
The fifth line finally causes an exception, as the Object returned cannot be cast as a String.