Sunday, September 30, 2007

instanceof doesn't work with Generics!

The information of generic types are not accessible at runtime. Contrary to what happens in other languages such us C++, this can be seen as a limitation that we, Java programmers, must bear with.

Simple scenario

As a sample scenario to show up the problem, we´ll write a simple generic Pair class containing a pair of objects of the same given parameter type T.
If you try to write a code such as this (overriding hashCode/equals methods):

public class Pair<T> {

private final T first, second;

public Pair(T first, T second) {
this.first = first;
this.second = second;
}

@Override public int hashCode() {
return first.hashCode() + second.hashCode();
}

@Override public boolean equals(Object obj) {
if (!(obj instanceof Pair<T>))
return false;
final Pair<T> other = (Pair<T>) obj;
if (this.first != other.first && (this.first == null || !this.first.equals(other.first))) {
return false;
}
if (this.second != other.second && (this.second == null || !this.second.equals(other.second))) {
return false;
}
return true;
}

public T getFirst() {
return first;
}

public T getSecond() {
return second;
}
}
You'll obtain a compiler error in the first line of the equals() method:
illegal generic type for instanceof
This error has to do with the way generics are implemented in Java and with the fact that the instanceof Java operator is evaluated at runtime (to check the runtime type information of an object).

The way Java works with Generics ends up within the compiler. This was a decision made up to be backward compatible with code previous to Generics. This feature is commonly known as Type Erasure. At compiler time, when a generic type is instantiated, all information about the actual parameter type is removed and therefore an instantiation of a type such as List<Employee> and List<Invoice> results at runtime in the same type, that is, its raw type List.

Therefore, you can't use the instanceof operator with generics. We replace this operator and we use instead the getClass() method for the implementation of the equals() method as follows:
  @Override   public boolean equals(Object obj) {   
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Pair<T> other = (Pair<T>) obj;
if (this.first != other.first && (this.first == null || !this.first.equals(other.first))) {
return false;
}
if (this.second != other.second && (this.second == null || !this.second.equals(other.second))) {
return false;
}
return true;
}
In addition to that, we'll obtain unchecked or unsafe operations when mixing raw types and generic types. In the above sample code we still have a warning at compile time:
Pair.java uses unchecked or unsafe operations
In order to get rid of this warning, we'll need to use the SuppressWarnings annotation just before the casting to Pair<T>:
...
@SuppressWarnings("unchecked")
final Pair<T> other = (Pair<T>) obj;
...
Conclusion

At runtime, the JVM hasn't information about generics, it is removed by the strategy known as Type Erasure.

However, avoiding the operator instanceof when using generics is only one sad consequence of Type Erasure. We can list some more:

  • You can't create an array of T (being T a type parameter).
  • You can't create an array of a generic type, such as new List<Employee>[7].
  • You can't access to the runtime class of a type parameter, such as T.class.
  • You can't access to the runtime Class of a generic type, such as List<Employee>.class.

You can read more consequences of Type Erasure in this blog.

The main benefit of Type Erasure is that they didn't had to change the JVM in order to deal with Generics! and therefore achieving backward compatibility. However there are people who doesn't like it and there's a new proposal to Java Generics (perhaps for Java 7!): Reified Generics, where generic parameters are available at runtime.