In a recent post, Arjan Blokzijl discussed how Class.getGenericSuperclass can be used to access generic type information.

This can be very useful, but implementing a general-purpose method to do this isn't as straightforward as it might seem. In this post, we'll outline some of the issues and hopefully resolve them, creating a utility class in the process. After all, with luck this is the kind of code that only needs to be written once! For the curious or impatient, here it is.

Before diving into the details, it should be noted that this utility can only access generic type information available in the class hierarchy at compile time; for example, the Agent in

class SecretAgentVehicle extends Vehicle<Agent>

Java's implementation of generics via type erasure means that it is not possible to access runtime type information - there is no way to get the Agent from

List<Agent> list = new ArrayList<Agent>();

as it is specified neither in the interface List nor ArrayList's superclass AbstractList. I'd love to be wrong about this, by the way - if you have some clever way of accessing this information, please let me know!

Now, as Arjan demonstrated, generic type information is available from ParameterizedType.getActualTypeArguments, where the ParameterizedType is returned by Class.getGenericSuperclass or Class.getGenericInterfaces (you'll need a cast from Type).

However, this only works if the parent is a generic class (respectively, if the class implements a generic interface). If the parent is a "normal" class, the Type returned will simply be the result of Class.getSuperclass (respectively, Class.getInterfaces) and you'll get a ClassCastException trying to cast to ParameterizedType. Ouch!

So what to do in a situation such as

class DoubleOhVehicle extends SecretAgentVehicle

in which we would presumably still like to know that DoubleOhVehicle has type parameter Agent? Well, you simply have to climb up the class hierarchy until you reach the desired class, and a loop would be one way to do this (a recursive implementation is also a good fit). Note that we assume that the input class is, indeed, a SecretAgentVehicle subclass, something that can be enforced using bounded type parameters, for instance.

assert SecretAgentVehicle.class.isAssignableFrom(clazz);

Type supertype = clazz;

do {
  supertype = supertype.getGenericSuperclass();
} while (!supertype.equals(Vehicle.class);

Type[] actualTypeArguments = ((ParameterizedType) supertype).getActualTypeArguments();

Or rather, it would be nice if you could write something like that...but it won't work. Unfortunately, Type doesn't have a getGenericSuperclass method - for the hiearchy traversal Class instances must be explicitly used.

Class<? extends SecretAgentVehicle> clazz;

while (!clazz.equals(SecretAgentVehicle.class)) {
  clazz = clazz.getSuperclass();
}

Type[] actualTypeArguments = 
  ((ParameterizedType) clazz.getGenericSuperclass()).getActualTypeArguments();

Voila! We can now cope with subclasses. What else could there be to do?

Well, consider the following class hierarchy:

class Activity<U, V>

class SecretAgentActivity<S> extends Activity<Agent, S>

class SecretAgentMission extends SecretAgentActivity<Mission>

class BondMission extends SecretAgentMission

Clearly, the (ordered!) type parameters of BondMission are Agent and Mission, but

  ((ParameterizedType) SecretAgentActivity.class.getGenericSuperclass()).getActualTypeArguments();

only returns [Agent, S]. Annoying as this may be, it is, indeed, all that can be expected: SecretAgentActivity.class has no way of knowing that the second parameter is Mission, since that is only defined in a subclass. Note also that the "uninstatiated" variable is returned as S rather than V, i.e. using the type parameter from the subclass.

In order to deal with this situation, therefore, we need not only to traverse up the hierarchy (bearing in mind that not all classes in the hierarchy are necessarily generic), but also keep track of type parameter/instance mappings as we go.
How does this work? Well, in the example, we can compare the type parameters for SecretAgentActivity.class with the actual type arguments to discover that parameter S is mapped to type Mission. Then, when examining the actual parameters [Agent, S] of Activity.class, we can use this mapping to "resolve" S to Mission, obtaining the desired result.

Since our aim is to write a general-purpose method, there is a further question we haven't addressed. Traversing the class hierarchy up from a given class is fine, but...when to stop? In the above example, the question "What is the type parameter for BondMission as a SecretAgentActivity (answer: [Mission])?" may be just as valid as "What are the parameters for BondMission as an Activity ([Agent, Mission])?"
For this reason, the method accepts two arguments: the class whose type arguments are required and the "context" class it is to be regarded a subclass of.

What if the target class and the "context" superclass are the same, or what if someone asks for the types of SecretAgentActivity.class as a subclass of Activity? Well, we know that not all the type variables will be available, but how should the code respond?

In this case, null will be returned for "unresolvable" type parameters (so SecretAgentActivity would have types [Agent, null] as an Activity subclass). This just seems like a reasonable response (besides being easy to implement), but could easily be changed, if desired.

Our discussion up until this point has focused only on class hierarchies. But with so many generic interfaces, our utility would be much more useful if the subclass or the context superclass, or both, could be interfaces. For instance, for a set of classes

interface AgentAttributes<V> extends Map<Agent, V>

interface AgentCodenames extends AgentAttributes<String>

class DigitCodenames implements AgentCodenames

we would also like to be able to answer

  • getActualTypeArguments(DigitCodenames.class, AgentAttributes.class) with [String] and
  • getActualTypeArguments(AgentCodenames.class, Map.class) with [Agent, String].

In principle, this should only require minor changes. One such is that interface type information is available via Class.getGenericInterfaces, not Class.getGenericSuperclass, and we'll need to extract the interface under consideration from the array of interfaces (not all of which are necessarily generic!) returned.

There is a more fundamental difference, though: the inheritance path from a class or interface to a superinterface is not necessarily unique! This is perfectly acceptable

interface Person

interface Assassin extends Person
interface Ladykiller extends Person

interface Agent extends Assassin, Ladykiller

where both [Agent, Assassin, Person] and [Agent, Ladykiller, Person] are paths from Agent to Person.

Which to choose? Well, thankfully, it doesn't matter in this case, because the compiler forbids conflicting (typed) inheritance paths, such as

interface Registry<T> extends Set<T>

interface CodenameRegistry extends Registry<String>
interface DoubleOhRegistry extends Registry<Byte>

interface Mi6Registry extends CodenameRegistry, DoubleOhRegistry

As a result, we can simply choose one of the paths, using ClassUtils.getSuperclassChain.

That link to the code once again.