How to resolve the actual type for a typical return type using reflection?

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

(My idea is to use this mechanism to determine the strategy using the interface and find the matching strategy (specific type of return type) from the set of strategy implementations at runtime without the need to introduce redundant helper methods that expose the type).

To be more specific, consider the 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 run time, I want to find out for a given class ( PersonDAOExtension.class ) whose type will be returned for the getById(..) method (expected to be: Person.class ).

Using reflection, I can find out which generic Type returned from this method. In this case, it is a TypeVariable (but it can also be Class if any class in the hierarchy indicates 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 that resolving the actual type would mean recursing to superclasses and interfaces and translating the original ( parameterizedType.getRawType() ) and actual ( parameterizedType.getActualTypeArguments() ) type arguments for any parameterized type until the type name is found .

Has anyone done this before, and maybe some code snippets are ready to help me achieve this? Thank you very much in advance:)


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

 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 
+6
source share
4 answers

Finally, I was able to find a solution recursive into superclasses and interfaces, replacing type variables with type arguments passed before the desired base class was reached:

  /** * 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) 

leads to Integer , Person

 resolveActualTypeArgs(AbstractDAO.class, DAO.class) 

leads to Integer , T

 resolveActualTypeArgs(LinkedList.class, Iterable.class, String.class) 

leads to String

Now I can use this to find out which of the given set of DAO implementations Persons can read:

 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 it works whether I new PersonDAOExtension() , a new PersonDAO() or new AbstractDAO<Person>{} .

+9
source

I was able to determine the common type of the returned method on a single line using the Google Guava TypeToken class:

 TypeToken.of(PersonDAOExtension.class) .resolveType(PersonDAOExtension.class.getMethod("getById", Integer.class).getGenericReturnType()) .getRawType() 

Alternatively, if you want to get a generic class type (as in your accepted answer) rather than the type of the returned method, you can 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 if this DAO can accept Persons. This can also be done with the API:

 (new TypeToken<DAO<?, Person>>() {}) .isSupertypeOf(TypeToken.of(PersonDAOExtension.class)) 

There is a worthy explanation of the capabilities of this and other Guava reflection utilities on the Guava website .

+8
source

I had a similar problem. In my solution, the abstract class had a method called

static void register(Class<? extends DAO> clazz, ? extends DAO daoInstance) .

And in the abstract class, I had a Map that stored references to instances. I used singleton, but you can use multimap if you have multiple instances. Using this technique, you can get rid of reflection, and you will have Set all your implemented implementations and their classes.

You can also register some pojo class if you need more information:

 public class DaoData{ private Class<? extends DAO> daoClass; private Class<?> someArbitraryTypeClass; // ... } 

static void register(DaoData daoData, ? extends DAO daoInstance)

I know that this is not the best solution, but it was simple and complete the task.

0
source

At run time, it returns an Object due to erasing styles, which replaces all the generic types of Object . At run time, it is not possible to determine which generic type was provided at compile time.

type-erase

-1
source

Source: https://habr.com/ru/post/948059/


All Articles