Gravity of Java Generics: Type Erasure Sucks
I must admit it now: Java implementation of Generics sucks. Yes, it is true. Even though I had nothing to do with creating the suckitude, I feel guilty for not recognizing acknowledging it up-front.
So what broke this camel's back? After all, I have been learning bits and pieces about how generics work for a while now, and finally feel competent to even explain how they work to others. I would even go as far as to claim I "understand Java generics" (phew -- that took a while -- better late than never!). Now: generics do make life bit easier; programming is bit more literal, and there are fewer casts even if there is more angle bracket pollution. So what if it's mostly syntactic sugar, fairy dust that is (mostly) swept away by compiler when writing bytecode out, not to be seen again?
Problem is this: although it is mostly ok that code can access the Type information only during compilation, this is poison for anything outside of the class that tries to make sense of the class after it has been compiled. That is, anything that would need Runtime (dynamic) Type Information. Especially so for things like data-binding tools that try to determine state of objects of certain class; and specifically when they try to peek into content type of container classes (Collections, Maps). Thing is: due to type erasure, all you know about that List is that, well, it is a List -- while compiler knows that you claim it is to contain, say, Foobarables, JVM only knows contents come in as "thing known as java.lang.Object"s. Bummer.
Is all lost? Almost, but not quite. Type erasure is... how did mr. Adams put it... [resulting classes are] "almost, but not quite, entirely" [without type information].
Just to illustrate the problem: you might think following makes sense:
List<String> result = binder.bind(inputSource, List<String>.class); // invalid, won't compile
But no: that won't compile. You can not squeeze a Class out of a generic type -- and even if you could, it would just be Thing Known As (in this case) List.class. (why? because typing has been, well, erased, result is the same old untyped List!). Teasingly, JDK actually does have Type classes under "java.lang.reflect" (including the thing that would serve us ok here, ParameterizedType!). But there are precious few ways of actually getting hold of one of these things, except for Class. Which, like we know by now, knows almost nothing about Type information.
But there is one sparkle of hope here. If you dig through JDK Javadocs, you will find that there are couple of hiding places where fragments of Type Information are actually hiding. Basically:
- No dynamic type information is available. It's gone, zilch, nada, nihil. Not even with "generic methods": those are just syntactic sugar.
- But some static type information is being stashed in the only place capable of hiding it: within the class definition.
That is: you can not generate type information by instantiating
generic instances, but you can actually generate and retain some by
declaring typed things. Specifically:
- Methods retain their (generic) return type information
- Method arguments likewise retain their type information
- Sub-classes retain information about generic type parameters used to parameterize their supertypes.
Now: that is not much. But it actually may be just enough to make some things work, as long as we can get hold of one of things mentioned above. The main remaining problem is that of bootstrapping: for example, once you can get hold of a "getter" (getXxx) or "setter" (setXxx) method, you can tease out type information, if any is declared in class sources. And following classes from thereon may allow typing to be uncovered, for properly declared classes and methods. But this will not help with the initial call, such as one used above. You could of course require a dummy class to be implemented, with dummy method with either argument or return types. Or, dummy class extending a parameterized class, and then accesing super type's type information from sub-class. But boy do these sound convoluted.
But there has to be a way. And as usual, where there is will, there is a way. So let's look at one clever solution! I mean, next time, in the follow-up entry. Yours, mr. Cliff Hanger.