Peter Walser Peter Walser - 7 months ago 12
Java Question

How do I resolve the actual type for a generic return type using reflection?

I have an interface with a method with a generic return type, and at runtime some instances of classes which indirectly implement that interface. Now I want to find out the actual return type for each implementation using reflection.

(My idea is to use this mechanism to define a strategy using an interface, and find a matching strategy (specific return type) out of a set of strategy implementations at runtime, without having to introduce redundant helper methods which expose the type).

To be more specific, let's consider following scenario:

private interface DAO <I extends Serializable, E> {

public E getById (I id);
}

private abstract class AbstractDAO <T> implements DAO<Integer, T> {

@Override
public T getById (Integer id) {
// dummy implementation, just for this example
return null;
}
}

private class PersonDAO extends AbstractDAO<Person> {
}

private class PersonDAOExtension extends PersonDAO {
}


At runtime, I want to find out for a given class (
PersonDAOExtension.class
) which type will be returned for the method
getById(..)
(expected to be:
Person.class
).

Using reflection, I can find out which generic
Type
is returned from this method. In this case, it's a
TypeVariable
(but could also be a
Class
, if any class in the hierarchy would specify a covariant return type):

Method method = PersonDAOExtension.class.getMethod("getById", Integer.class);
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof TypeVariable<?>) {
TypeVariable<?> typeVariable = (TypeVariable<?>) genericReturnType;
typeVariable.getName(); //results in "T"
}


I assume resolving the actual type would mean recursing into superclasses and interfaces and translate the raw (
parameterizedType.getRawType()
) and actual (
parameterizedType.getActualTypeArguments()
) type arguments for any parameterized type, until the desired type name is found.

Has anyone done this before, and maybe some code snippets ready which will help me achieve this? Many thanks in advance :)




Hint: I was able to extract following information at runtime using reflection, so the raw and actual type information is retained:

private abstract interface DAO<I, E>
private abstract class AbstractDAO<T> extends Object implements DAO<Integer, T> [raw type:DAO<I, E>]
private class PersonDAO extends AbstractDAO<Person> [raw type:AbstractDAO<T>]
private class PersonDAOExtension extends PersonDAO

Answer

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>{}.