Unifying Type Parameters in Java

October 12, 2009

At kaChing, we’ve designed from the ground up using services. We have many servers clustered into services which respond to simple queries such as “give me Alice’s watchlist”, “buy 10,000 shares of Google for Ted” and quite often “give you $10M for inviting a friend”. At the lowest level in our platform, we’ve decomposed our server’s work into queries that all have the interface

interface Query<T> {  T process();}

Since this interface has other methods, we’ve defined abstract classes that offer default behavior. The most popular is AbstractQuery<T>, and using it we wrote a query named Version which returns, you guessed it, the version of the server running. Its type hierarchy is as follows.

Version extends AbstractQuery<String>

and

AbstractQuery<T> implements Query<T>

So what’s the big deal? So far so good. But here’s the trick. We want to pick specific serializers based on queries’ response types (the T of Query<T>). And we want to infer the response types from the queries’ class definitions.

Type getQueryReturnType(Class<? extends Query<?>> clazz) { ...

First, it is important to understand the difference between a Type and a Class. Java’s type system is rich: primitive types, object types, parametric polymorphism, nominal subtyping and such. However, these high-level concepts map to a rather simple bytecode representation. For instance, a well-know simplification is the erasure of parameter types and their inexistence at run-time.
A Type is roughly the compile time representation of a Java type, whereas a Class represents a compiled Java class where the type information has been erased. Luckily, some type parameters are written in the class’ bytecode — namely generic type parameters. (This is the key for super type tokens.)
The solution is to unify the type’s full inheritance hierarchy between Version and Query<T>. We have the four elements

  • Version
  • AbstractQuery which is a concrete instance of
  • AbstractQuery<T> which implements
  • Query<T>

Try to picture a chain linking String to T in AbstractQuery<T> and linking that same T to the one in Query<T>. Unification is the process of discovering the chain and resolving for T.
Enough theory, let’s get down to business. In Java, you can call

abstractQueryWithString =(ParametrizedType) Version.class.getSuperclass();

which returns the representation of AbstractQuery. We know that

Query.class.getTypeParameters()[0]

represents the type parameter T in Query<T>. Find this type parameter in the array

abstractQueryWithString.getTypeParameters();

and use its index in the parallel array

abstractQueryWithString.getActualTypeArguments()

to get the result! You’ve found that the return type of the query Version is the type String. The full solution is available as part of the JsonMarshaller project.

Oh, and by the way, if you love this kind of stuff we’re recruiting.

Posted for Pascal-Louis Perez