Java generics and type erasure

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
// create an array of strings
String[] strings = new String[10];

// cast it to an array of objects
Object[] objects = strings;

// insert an object into the array
objects[0] = new Object();

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
// create a vector of strings
Vector<String> strings = new Vector<String>(10);

// cast it to a vector of objects
Vector<Object> objects = (Vector<Object>)strings;

// insert an object into the vector
objects.add(new Object());

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
// a generic class with a method to create an array of the parameterised type
public class GenericArrayTest<T>{

  // returns an array of the parameterised type
  public <T> T[] returnArray(){
    return new T[10];
  }

}

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
// a generic class with a method to create an array of the parameterised type
public class GenericArrayTest<T>{

  // declare the class instance
  private Class<T> tClass;

  // code to initialise tClass

  // returns an array of the parameterised type
  public <T> T[] returnArray(){
    return (T[])java.lang.reflect.Array.newInstance(tClass, 10);
  }

}

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
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.

Casting to sub-types

Consider the following code:

1
2
3
4
5
// create an array of objects
Object[] objects = new Object[10];

// cast it to an array of strings
String[] strings = (String[])objects;

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
// create a vector of strings
Vector<String> strings = new Vector<String>(10);

// cast the vector to a generic vector
Vector objects = strings;

// insert an object into the vector
objects.add(new Object());

// fetch an object from the vector of strings
Object anObject = strings.get(0);

// fetch a string from the vector of strings
String aString = strings.get(0);

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.

Where now?

Found this useful? Share it:

Also in Articles: