September 11, 2010

Java generics are not real generics

Tags: Java, Technical

Java 5 added generics into the language. This allowed programmers to specify class types as parameters to other classes and maintain type safety. For instance Set<Integer> is a set of integers and if you try to add a non-integer to it, a compile time error should occur.

Java had been around a while by the time generics were added. Unfortunately it was considered too hard to add generics to the Java bytecode that forms a running Java program. Instead generics are just syntactic sugar. When a Java program is compiled the generics are checked, but after that they are stripped out. The bytecode that is run does not know anything about generics. This means it is possible to violate the generics constraints in the code at run-time (because the constraints don’t exist at run-time).

Below is an example demonstrating the problem. A set of integers is created, but then using reflection a string is added to the set. This is clearly a violation of the set’s definition. However, reflection is a run-time process and the compiler is not smart enough to detect the potential error at compile-time (which would be very hard to do). If the set is now inspected (and not checked carefully), it will look like the integer 1 is in the set, rather than the actual string “1”. At this point various errors can occur, from the subtle contains error below, to outright ClassCastException if elements are extracted from the set as integers. As an aside, reflection is very cool and a great tool, but from my experience, most of the time it is the wrong tool.

Set<Integer> integerSet = new HashSet<Integer>();
Method addMethod = integerSet.getClass().getMethod("add", Object.class);
addMethod.invoke(integerSet, "1");
integerSet.contains(1); // -> false

Remember, Java generics are not real generics. In particular pay special attention to using reflection on generic objects.