Friday 28 January 2011

Type erasure with generics

Type-safety checking of generic types is only performed at compile time. Type information is then removed and will not appear in the byte code. Consequently, a generic type like ArrayList<String> is translated to its ArrayList equivalent...

List<String> strings = new ArrayList<String>();
List<Integer> nums = new ArrayList<Integer>();
System.out.println(strings.getClass());
System.out.println(nums.getClass());

Both will print java.util.ArrayList. Type erasure means generic type information will not be available at runtime, and the following attempt to create an array of a generic type will not compile...
TreeSet<String>[] families = new TreeSet<String>[5];

The wildcard type is an exception to this :
List<?>[] info = new List<?>[5];

The ? stands for an unknown type that matches anything.

There is however, an upside to type erasure – there will be interoperability between generic type and raw types...

TreeSet<String> names = new TreeSet<String>();
TreeSet rawTree = names; //names is widened to raw type

Type-safety checking can be defeated, typically for backward compatibility. The following will compile, but with an “unchecked cast” warning...



This also works...

import java.lang.reflect.*;
...
TreeSet<String>[] people = (TreeSet<String>[])Array.newInstance(TreeSet.class,5);


But the way is now open for ClassCastException at runtime...
TreeSet rawTree = people[0];
oldTree.add(new Integer(1)); //compiles, but runtime error
Compile-time type-safety check warnings can be suppressed if needed...
@SuppressWarnings("unchecked")
public static void main (String[] args) {
...

}

Runtime operations such as instanceof are not permitted again, because of type erasure.

Similarly, a cast such as...
TreeSet<String> handles = (TreeSet<String>)name;
would produce an unchecked compiler warning because it is not something the run-time system can safely perform.

But why java uses Type erasures?
Type erasure is not a fault, rather it was clever step taken by java designers.
The reason why Java uses type erasure is to maintain backwards compatibility. Let's assume you wrote some code a few years back which used Java Lists, before generics was introduced in the language. Obviously your code had no mention of generics. Now, when engineers at Sun decided to introduce generics, they did not want to break code which people had already written.
One possible way was to ensure that client code which uses generics in the classes they invoke, never carries any information about generics in the compiled code. So the above class when compiled should not carry any information about generics. Because compiled client code never carries information about generics anyways, library implementers are free to add generics to their code without the worry of breaking anyone's old code.

Generic types are not covariant : Generics allow you to use only one type

All the Object-based collection types (known as raw types) in classic Java are now retrofitted as generic types and accept type parameters. Generic types allow you to express your intent as being restricted to a particular type when creating and using it...

TreeSet<String> names = new TreeSet<String>();
names.add("Fred");
names.add("Wilma");
names.add("Pebbles");

SortedSet<String> is called the parameterized type and String is the actual type argument. Type-safety checking will be performed at compile time...
names.add(new Integer(100)); //compilation error

The compiler will also reject the following...
TreeSet<Object> names = new TreeSet<String>();
TreeSet<Object> alias = (TreeSet<Object>)names;


This is because generic types are not covariant – a TreeSet of String references is not a TreeSet of Object references.
With generic types, retrieved elements do not require explicit conversion...