Java反射:获取参数化泛型超类方法的正确类型

Whe*_*zil 3 java generics reflection

我使用反射来发现类及其超类的方法,以及方法参数和返回值的类型。这大部分都有效,但我在处理一些特定的通用情况时遇到了麻烦。假设我有一个基类:

package net.redpoint.scratch;
public class Base<E> {
    public E getE() { return null; }
}
Run Code Online (Sandbox Code Playgroud)

还有一个子类:

package net.redpoint.scratch;
public class Derived extends Base<String> {}
Run Code Online (Sandbox Code Playgroud)

使用反射,我可以遍历 Derived 的方法并获取 arg 和返回类型(代码省略,但工作正常)。不过,我也想知道继承的方法。使用下面的代码,我可以非常非常接近。但是我无法获得 getE() 的正确返回类型。我可以获得通用类型“E”,但不能获得实际类型“java.lang.String”:

package net.redpoint.scratch;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
public class Scratch {
  public static void main(String[] args) throws Exception {
    Class clazz = Class.forName("net.redpoint.scratch.Derived");
    Type superType = clazz.getGenericSuperclass();
    if (superType instanceof ParameterizedType) {
      ParameterizedType superPt = (ParameterizedType)superType;
      Type[] typeArgs = superPt.getActualTypeArguments();
      Type t0 = typeArgs[0];
      // This is "java.lang.String"
      System.out.println(t0.getTypeName());
      Type superRawType = superPt.getRawType();
      if (superRawType instanceof Class) {
        Class superRawClazz = (Class)superRawType;
        for (Method method : superRawClazz.getDeclaredMethods()) {
          if (method.getName().equals("getE")) {
            Type returnType = method.getGenericReturnType();
            if (returnType instanceof TypeVariable) {
              TypeVariable tv = (TypeVariable)returnType;
              // This is "E"
              System.out.println(tv.getName());
              // How do I associate this "E" back to the correct type "java.lang.String"
            }
          }
        }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

输出是:

java.lang.String
E
Run Code Online (Sandbox Code Playgroud)

我的问题是,如何找到“E”实际上是“java.lang.String”?我怀疑它与查找 typeArgs[] 有关,但我不知道如何到达那里。除非您实际使用过泛型反射,否则请不要回复。我看到很多帖子的答案都是“类型擦除可以防止这种情况发生”,但这是不正确的。

Rad*_*def 5

我建议为此使用番石榴。例如:

Type returnType =
    new TypeToken<Derived>() {}
        .resolveType(Base.class.getMethod("getE").getGenericReturnType());
Run Code Online (Sandbox Code Playgroud)

TypeToken.resolveType(Type)

另一种选择相当复杂,但也是可能的。您需要自己遍历层次结构并将类型变量映射到类型参数。

处理最琐碎的情况会是这样的:

static Type getArgument(TypeVariable<?>   var,
                        ParameterizedType actual) {
    GenericDeclaration decl = var.getGenericDeclaration();
    if (decl != actual.getRawType())
        throw new IllegalArgumentException();
    TypeVariable<?>[] vars = decl.getTypeParameters();
    for (int i = 0; i < vars.length; ++i) {
        if (var == vars[i]) {
            return actual.getActualTypeArguments()[i];
        }
    }
    return null;
}
Run Code Online (Sandbox Code Playgroud)

如果你有这样的事情,这种简单的方法就会失败:

abstract class AbstractDerived<T> extends Base<T> {}
class Derived extends AbstractDerived<String> {}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您需要首先映射EfromBaseTfrom AbstractDerived,然后映射TString,因此该方法必须递归或迭代超类型。我在这里有一个更复杂的示例,但由于多种原因,该示例仍然是错误的。

您将遇到的另一个障碍是,要返回一个替换了类型变量的新类型,您需要自己实现和ParameterizedType,或者使用未记录的类。GenericArrayTypeWildcardTypesun.reflect

所有这些都是说你真的应该只使用已经处理这些东西的 Guava ,除非你TypeToken出于某种原因正在做一些类似自己编写的事情。

所有这些的警告(您似乎已经知道)是,所有这些都取决于在子句中提供给超类型的实际类型参数extends(显式或隐式,如匿名类中)。如果您只是这样做,new Base<Double>()则无法在运行时恢复类型参数。

  • 啊,它的 TypeToken.of(clazz) (2认同)