【问题标题】:How do I resolve the actual type for a generic return type using reflection?如何使用反射解析泛型返回类型的实际类型?
【发布时间】:2013-06-25 12:25:47
【问题描述】:

我有一个接口,其中有一个带有通用返回类型的方法,并且在运行时有一些类实例间接实现了该接口。现在我想使用反射找出每个实现的实际返回类型

(我的想法是利用这种机制,通过接口定义策略,在运行时从一组策略实现中找到匹配的策略(具体返回类型),而不必引入冗余的辅助方法。暴露类型)。

更具体地说,让我们考虑以下场景:

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

在运行时,我想找出给定类 (PersonDAOExtension.class) 将为方法 getById(..) 返回的类型(预计为:Person.class)。

使用反射,我可以找出这个方法返回了哪个泛型Type。在这种情况下,它是 TypeVariable(但也可以是 Class,如果层次结构中的任何类会指定协变返回类型):

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"
}

我假设解析实际类型意味着递归到超类和接口,并将 raw (parameterizedType.getRawType()) 和 actual (parameterizedType.getActualTypeArguments()) 类型参数转换为任何参数化类型,直到找到所需的类型名称。

以前有没有人这样做过,也许有一些代码 sn-ps 可以帮助我实现这一点?非常感谢提前:)


提示:我能够在运行时使用反射提取以下信息,因此保留了原始和实际的类型信息:

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

【问题讨论】:

  • 一旦你知道类型,你想对它做什么?如果您打算使用 instanceof 运算符,您可以改用 if (Person.class.isAssignableFrom(method.getReturnType())) { ... }
  • (非泛型)返回类型是 Object,所以这总是返回 false...

标签: java generics reflection


【解决方案1】:

我终于找到了解决方案,递归到超类和接口,用传递的类型参数替换类型变量,直到到达所需的基类:

 /**
 * 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;
}

像魅力一样工作:

resolveActualTypeArgs(PersonDAOExtension.class, DAO.class)

结果为@​​987654324@、Person

resolveActualTypeArgs(AbstractDAO.class, DAO.class)

结果为@​​987654327@、T

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

结果为@​​987654330@

我现在可以使用它来找出一组给定的 DAO 实现中的哪些可以读取 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]);
}

无论我传递new PersonDAOExtension()new PersonDAO() 还是new AbstractDAO&lt;Person&gt;{},这都有效。

【讨论】:

  • 不错!仅供参考,我找到了一个提供类似功能但更通用的小型库:gentyref。我刚才试了一下来解决我的问题(当子类缩小泛型类型时更精确地解析字段类型),它看起来很有希望。泛型有很多极端情况,我不认为 gentyref 涵盖了所有情况,但这是一个很好的开始。
  • 刚刚使用交换的通用参数进行了测试 (interface CrazyMap&lt;K, V&gt; extends Map&lt;V, K&gt;) 它有效!极好的。非常感谢!
【解决方案2】:

我能够使用 Google Guava 的 TypeToken 类在一行中确定方法的通用返回类型:

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

或者,如果您想要获取类的泛型类型(就像您在接受的答案中所做的那样),而不是方法的返回类型,您可以执行以下操作:

TypeToken.of(PersonDAOExtension.class)
        .resolveType(AbstractDAO.class.getTypeParameters()[0])
        .getRawType()

这两种解决方案都按预期返回 Person.class

从接受答案的 cmets 看来,您似乎只想知道给定的 DAO 是否可以接受 Person 对象。这也可以通过 API 实现:

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

关于这个和其他 Guava 反射实用程序的功能有一个不错的解释 on the Guava GitHub wiki

【讨论】:

    【解决方案3】:

    我之前也遇到过类似的问题。在我的解决方案中,抽象类有一个名为

    的方法

    static void register(Class&lt;? extends DAO&gt; clazz, ? extends DAO daoInstance).

    在抽象类中,我有一个Map,它存储了对实例的引用。我使用了单例,但如果您有多个实例,您可以使用多图。使用这种技术,您可以摆脱反射,您将拥有您注册的所有实现及其类的Set

    如果您需要更多信息,也可以注册一些 pojo 类:

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

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

    我知道这不是最好的解决方案,但它很简单并且可以完成工作。

    【讨论】:

    • 那个名为 register 的方法的返回类型是什么?
    • 我认为是void
    猜你喜欢
    • 2017-06-07
    • 1970-01-01
    • 1970-01-01
    • 2021-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-16
    相关资源
    最近更新 更多