How do I resolve the actual type for a generic return type using reflection?
I was finally able to find a solution, recursing into super classes and interfaces, replacing type variables with the type arguments passed until reaching the desired base class:
/**
* Resolves the actual generic type arguments for a base class, as viewed from a subclass or implementation.
*
* @param <T> base type
* @param offspring class or interface subclassing or extending the base type
* @param base base class
* @param actualArgs the actual type arguments passed to the offspring class
* @return actual generic type arguments, must match the type parameters of the offspring class. If omitted, the
* type parameters will be used instead.
*/
public static <T> Type[] resolveActualTypeArgs (Class<? extends T> offspring, Class<T> base, Type... actualArgs) {
assert offspring != null;
assert base != null;
assert actualArgs.length == 0 || actualArgs.length == offspring.getTypeParameters().length;
// If actual types are omitted, the type parameters will be used instead.
if (actualArgs.length == 0) {
actualArgs = offspring.getTypeParameters();
}
// map type parameters into the actual types
Map<String, Type> typeVariables = new HashMap<String, Type>();
for (int i = 0; i < actualArgs.length; i++) {
TypeVariable<?> typeVariable = (TypeVariable<?>) offspring.getTypeParameters()[i];
typeVariables.put(typeVariable.getName(), actualArgs[i]);
}
// Find direct ancestors (superclass, interfaces)
List<Type> ancestors = new LinkedList<Type>();
if (offspring.getGenericSuperclass() != null) {
ancestors.add(offspring.getGenericSuperclass());
}
for (Type t : offspring.getGenericInterfaces()) {
ancestors.add(t);
}
// Recurse into ancestors (superclass, interfaces)
for (Type type : ancestors) {
if (type instanceof Class<?>) {
// ancestor is non-parameterized. Recurse only if it matches the base class.
Class<?> ancestorClass = (Class<?>) type;
if (base.isAssignableFrom(ancestorClass)) {
Type[] result = resolveActualTypeArgs((Class<? extends T>) ancestorClass, base);
if (result != null) {
return result;
}
}
}
if (type instanceof ParameterizedType) {
// ancestor is parameterized. Recurse only if the raw type matches the base class.
ParameterizedType parameterizedType = (ParameterizedType) type;
Type rawType = parameterizedType.getRawType();
if (rawType instanceof Class<?>) {
Class<?> rawTypeClass = (Class<?>) rawType;
if (base.isAssignableFrom(rawTypeClass)) {
// loop through all type arguments and replace type variables with the actually known types
List<Type> resolvedTypes = new LinkedList<Type>();
for (Type t : parameterizedType.getActualTypeArguments()) {
if (t instanceof TypeVariable<?>) {
Type resolvedType = typeVariables.get(((TypeVariable<?>) t).getName());
resolvedTypes.add(resolvedType != null ? resolvedType : t);
} else {
resolvedTypes.add(t);
}
}
Type[] result = resolveActualTypeArgs((Class<? extends T>) rawTypeClass, base, resolvedTypes.toArray(new Type[] {}));
if (result != null) {
return result;
}
}
}
}
}
// we have a result if we reached the base class.
return offspring.equals(base) ? actualArgs : null;
}
Works like a charm:
resolveActualTypeArgs(PersonDAOExtension.class, DAO.class)
results in Integer
, Person
resolveActualTypeArgs(AbstractDAO.class, DAO.class)
results in Integer
, T
resolveActualTypeArgs(LinkedList.class, Iterable.class, String.class)
results in String
I can now use this to find out which of a given set of DAO implementations can read Persons:
List<DAO<?, ?>> knownDAOs = ...
for (DAO<?, ?> daoImpl : knownDAOs) {
Type[] types = resolveActualTypeArgs(daoImpl.getClass(), DAO.class);
boolean canReadPerson = types[1] instanceof Class<?> && Person.class.isAssignableFrom((Class<?>) types[1]);
}
And this works regardless of whether I pass a new PersonDAOExtension()
, a new PersonDAO()
or a new AbstractDAO<Person>{}
.
I was able to determine the generic return type of a method in one line using Google Guava's TypeToken class:
TypeToken.of(PersonDAOExtension.class)
.resolveType(PersonDAOExtension.class.getMethod("getById", Integer.class).getGenericReturnType())
.getRawType()
Alternatively, if you want to get the generic type of a class (as you did in your accepted answer), rather than the return type of a method, you could do the following:
TypeToken.of(PersonDAOExtension.class)
.resolveType(AbstractDAO.class.getTypeParameters()[0])
.getRawType()
Both of these solutions return Person.class
as expected.
From your comments on the accepted answer, it looks like you just want to know whether the given DAO can accept Person
objects. This too is doable with the API:
(new TypeToken<DAO<?, Person>>() {})
.isSupertypeOf(TypeToken.of(PersonDAOExtension.class))
There is a decent explanation of the capabilities of this and other Guava reflection utilities on the Guava GitHub wiki.