Type erasure is a term many people in the Java world do not know or understand. I think it is important to at least understand what it means…not the byte code behind it, but the semantics and its effect on your application. I’ve talked about this in other blog posts here, but today I figured I would demonstrate by a few code samples.
To start, see the following code samples and take the test:
Now that you have your answers, let’s compare to the actual answers and an explanation of why.
The answer to this question is fairly straightforward and easy: compile time error. Java ensures at “compile time” that types are preserved according to their definition. Hence, you cannot put an integer into a list of strings (or can you?).
If you look at the source assist in your IDE, it may claim that the ‘add’ method on
add(String). Thus, in theory, you should be able to reflectively look up that method, correct? If you said yes, then you will find that executing that code results in a
NoSuchMethodException. The reason for this is type erasure. The Java compiler strips the type information away so that at runtime, the list is just a list. Thus, the definition of list is
Object is the lowest boundary of the type definition of
List. If the class had been defined as
List, then the lowest boundary would be
Number, rather than
Now that we know you have to reflectively look up the method by Object, what happens when you invoke that method passing in an Integer for example, rather than a String. In test 1, we saw the compiler throws a compiler error saying invalid. So, will the JVM throw an error if we attempt to stuff an integer into a list of strings, reflectively? The answer is that no error will be thrown and in fact, the integer will get stored in the list without issue. This is surprising to many. However, it stems from type erasure again, which stems from providing backwards compatibility with the several thousand third party libraries we all use that still run JDK 1.4. Being the Java compiler erases the types at runtime, the JVM has no notion that the list is a list of strings. To the JVM, it is just a list of objects, so an integer satisfies that constraint.
Now that we know the list can reflectively add an integer, what happens if we try to retrieve that element as a String, which is perfectly valid compile time code since the compiler assumes it is still a list of strings? The answer this time is that a
ClassCastException is thrown. The JVM behind the scenes via code the compiler generates automatically performs a cast of the returned object to a String. Remember that the JVM has no type information, so when
get(0) is invoked, it returns an
Object. For the compiled code to work, it has to insert a cast statement for you. However, since that object is actually an integer we reflectively inserted, it fails to cast, throwing the exception.
So how did you do? The important thing to remember is that generics in Java are purely syntactic sugar and at runtime they are just like any other list prior to JDK 1.5. This does not mean you should not use generics; rather you should. It is just important to understand potential impacts that could arise. For example, if someone decides to reflectively operate on your list (or maybe even some custom class with generics you define), then you lose control of what is in your list.
It should be noted that there are talks in the Java community about possibly including real templates/generics that preserve type information at runtime in a later release. JDK 8/9 maybe? Until that day, take care.