Saturday, April 23, 2011

Using Guice to Retrieve Parameterized Types at Runtime

At every point in a Java coder's life they have tried to write a class that does something like the following:

Of course, E.class is not going to fly with Java, as the parameterized type E is only a compile time check.

Let's look at a more real world example. Let's say I want to define a Function type which is parameterized by an Input (I) and Output (O) type:

This type of thing turns out to be very useful, as functions aren't first class citizens in Java. Once you have a class like this it's natural to want to do things such as filter a collection of Function objects based on either Input or Output type, automatically construct processing chains of Functions, etc. To do this we'd probably need to change our Function definition to look like the following, so we can retrieve the Input/Output types at runtime:

The problem we immediately see with this is that we can't implement those functions at this level, for the same reason as my initial simple example. One option is to make these two functions abstract, but just by looking at an example Function implementation we see how sub-optimal this is:

The additional functions we've forced on the users of our abstract class is even worse if we think about creating anonymous inner functions from this interface and all the extra code clutter. We also see that they return trivial values and are pretty redundant given these types are already specified in the extends clause (if only we could get that information).

Thankfully Guice provides us a nice clean way around this problem, allowing us to move those methods back into the abstract Function class, where we wanted them all along, by injecting TypeLiteral objects. This makes our new Function class look like this:

Now if we use this class in the following way:

Then the printed output is, as expected:

Double2String: class java.lang.Double --> class java.lang.String
String2Object: class java.lang.String --> class java.lang.Object

The beauty of this is it is very simple to inject the types you want when you want them, and this can be applied anywhere you need the runtime type information the compiler erases.

Disclaimer: The above code is intended to be illustrative and was not thoroughly tested.

No comments:

Post a Comment