Javareflection:获取正确类型的参数化通用超类方法

我使用reflection来发现类及其超类的方法,以及方法参数和返回值的类型。 这大部分都有效,但我遇到了一些特定的通用案例。 假设我有一个基类:

package net.redpoint.scratch; public class Base { public E getE() { return null; } } 

还有一个子类:

 package net.redpoint.scratch; public class Derived extends Base {} 

使用reflection我可以遍历Derived的方法并获取arg和返回类型(代码省略,但它工作正常)。 但是,我也想知道inheritance的方法。 使用下面的代码,我可以非常非常接近。 但是获得getE()的正确返回类型是我的选择。 我可以得到generics类型“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" } } } } } } } 

输出是:

 java.lang.String E 

我的问题是,我怎么发现“E”实际上是“java.lang.String”? 我怀疑它与查找typeArgs []有关,但我不知道如何到达那里。 请不要回应,除非你真的使用genericsreflection。 我看过很多post都有“类型擦除阻止这个”的答案,但事实并非如此。

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

 Type returnType = new TypeToken() {} .resolveType(Base.class.getMethod("getE").getGenericReturnType()); 

请参见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; } 

如果您有这样的事情,那种简单的方法将会失败:

 abstract class AbstractDerived extends Base {} class Derived extends AbstractDerived {} 

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

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

所有这一切都是说你应该真的只使用已经处理过这些东西的Guava,除非你出于某种原因编写自己的TypeToken

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

诀窍是,从原始类获取类型参数,并将其与分析返回类型或类型参数时获得的类型变量相关联:

 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 superRawType = superPt.getRawType(); if (superRawType instanceof Class) { Class superRawClazz = (Class)superRawType; TypeVariable[] typeParms = superRawClazz.getTypeParameters(); assert typeArgs.length == typeParms.length; Map typeMap = new HashMap<>(); for (int i = 0; i < typeArgs.length; i++) { typeMap.put(typeParms[i], typeArgs[i]); } for (Method method : superRawClazz.getDeclaredMethods()) { if (method.getName().equals("getE")) { Type returnType = method.getGenericReturnType(); if (returnType instanceof TypeVariable) { TypeVariable tv = (TypeVariable)returnType; Type specializedType = typeMap.get(tv); if (specializedType != null) { // This generic parameter was replaced with an actual type System.out.println(specializedType.toString()); } else { // This generic parameter is still a variable System.out.println(tv.getName()); } } } } } } } } 

你的结局是错误的概念。 getE()的签名已在类中定义。

Derived不会覆盖或以任何方式修改该方法。 换句话说:该方法被定义为返回E 扩展Base并提供特定类型的事实不会影响该方法的定义!

因此,您尝试进入“已更改”的返回类型是徒劳的。 因为返回类型没有改变。 相关 – 请看这里:

  Class superClazz = Class.forName("Derived").getSuperclass(); for (TypeVariable clz : superClazz.getTypeParameters()) { System.out.println(clz); } Class clazz = Class.forName("Derived"); Type superType = clazz.getGenericSuperclass(); if (superType instanceof ParameterizedType) { ParameterizedType superPt = (ParameterizedType) superType; for (Type t : superPt.getActualTypeArguments()) { System.out.println(t.getTypeName()); } } 

以上打印:

 E F java.lang.String java.lang.Integer 

(对于我重新编写的示例,我使用了两个参数E,F并将它们替换为String,Integer)