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 ofAbstractQuery<T>
which implementsQuery<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