Today I was writing some interceptors in Java, and I wanted to be able to filter the input based on whether it implemented a given interface or otherwise. Class<?> exposes a very handy isAssignableFrom method for this, but it doesn’t look very nice, so I tried to go for something a bit neater, like:
1: Feature.<TargetFeature>isImplementedBy(interceptedInstance);
Neat, and much more readable. I didn’t quite make it there, but got close enough for my purposes, and had some (very little) deeper knowledge of generics inflicted upon me for my sins.
They’re generics, Jim, but not as we know them
I’m more still under the influence of .Net coding, so the first stab at the above was to write it as it would have been in C#:
1: public class Feature<T>
2: {
3: public static bool IsImplementedBy(object o) { return o is T; }
4: }
but in Java …
1: public class Feature<T> {
2:
3: public static boolean isImplementedBy(Object instance) {
4: return T.isAssignableFrom(instance.getClass());
5: }
6: }
Which is all well and good except for the fact that that won’t work. In fact, it won’t even compile: T is treated as an instance field in Java, and it’s not accessible from a static method. Oh well, we can always stick the generic type directly onto the method itself, can’t we?
Not really.
First, to get direct access to the isAssignableFrom method, we’d have to make T extend Class<?>. This class is final, so that’s a no go. Second, Java doesn’t really allow you to do much of anything weird with T. This is rather annoying – you can’t even create a new instance of it. Some googling revealed the reason the generics in the two languages behave so differently: they swing in very different ways.
If we open up the dll from the C# snippet in Reflector, we’ll see that the class is unchanged. This tells us the C# compiler left in all the information about the generic parameter. In turn, this lets the run time play with it more easily.
On the other hand, opening the Java class with DJ decompiler shows no sign of the generic parameter. Anywhere. What gives?
Type erasure
It turns out that when Java generics are processed at compile time, the compiler pulls a trick called type erasure, and converts all generic types into raw types. So, regardless of how many Feature<X>s, Feature<Y>s, or Feature<Z>s we create , we’re still going to have a lone, raw Feature class after compilation. In fact, you will notice that you can cast stuff a List into a List<Whatever> without casting, though you get a warning – they’re going to be the same thing once they’re compiled.
It also means that there’s no way to determine the generic type at runtime – that is, we cannot look at a class at runtime and know what type it’s using.
Finally, it means that there is only one static scope for all variations of our generic class – static variables are shared between all of them. (Whether we should be using static variables is another question really). They are, after all, the same class.
Oh well. Eventually I settled for a factory method that spits out Feature instances:
1: Feature.ofType(TargetFeature.class).isImplementedBy(interceptedInstance);
which is a bit more verbose than I originally wanted; unless I missed something, that’s the closest I’m going to get for now. It gets the job done, and it’s not totally fugly.
Nice article, I like the approach. I knew about the genetics differences between the two lingos, quite a pain really and I much prefer the .net way, but that is only possibble since the vm is not backward compatible, unlike the jvm :/
excuse my iPhone for the possibly horrendous generic comment above :/