Monday, November 16, 2015

Java Generics Note: Reification vs. Covariance

During my study of Java generics, two terms often comes up are reification and covariance.

Reification means the resolution of a instance's exact type at run time. A type is reifiable if JVM can tell the type given an instance at runtime, otherwise we say the type is unreifiable. All generic types are not reifiable, except when it's a unbounded wildcard type or raw type. This is because the Java compiler erases all type argument information with a procedure called type erasure. On the other hand, array types are reifiable, because the element type is available at run time.

Covariance is a property about deriving a set of types given a set of basic types. For instance, for any reference type T, List<T> and T[] are types of its own. Such a derived set of types is said to be covariant they inherit the type compatibility of the basic types. In this case, List<T> is not covariant, because List<Integer> is not a subtype of List<Object>

To conclude, generic types are unreifiable and incovariant, whereas array types are reifiable and covariant. One might ask, can we have other combination of this properties? Such as a set of types that are reifiable and incovariant, or unreifiable and covariant? To answer this question, one need to first understand an very important property that guides the design of strongly type language, such as Java, which is type-safety. For generic types, it means that the program shall not call List<Integer>'s add() method with a String type argument. For array, it means the program shall not call arr[i] = "foobar" if the type of arr is Integer[].

Generic types such as List<T> is unreifiable, meaning that the runtime can't tell List<String> from List<Integer>. Let's assume that List<T> is covariant, this means that List<String> and List<Integer> will both be a subtypes of List<Object>

List<Object> = new List<Integer>()
List<String> strList = (List&ltString>) intList; // Allowed as a down cast.
strList.add(100);
strList.get(0).length();

The issue happen at runtime. Since List<T> is unreifiable, JVM can't tell that line 3 is wrong, so no runtime exception will be thrown.However, line 4 will fail because Integer doesn't have a length() method (in reality a ClassCastException is thrown because Java compiler adds a cast before calling length()). Therefore, a unreifiable type have to also be incovariant to avoid issues like this.