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.