简体   繁体   English

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

[英]Java reflection: getting correct type of parameterized generic superclass method

I am using reflection to discover methods of classes and their superclasses, along with the types of method arguments and return values. 我使用反射来发现类及其超类的方法,以及方法参数和返回值的类型。 This mostly works, but I'm having trouble with some specific generic cases. 这大部分都有效,但我遇到了一些特定的通用案例。 Suppose I have a base class: 假设我有一个基类:

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

And a subclass: 还有一个子类:

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

Using reflection I can walk through the methods of Derived and get arg and return types (code omitted, but it works fine). 使用反射我可以遍历Derived的方法并获取arg和返回类型(代码省略,但它工作正常)。 However, I also want to know the inherited methods. 但是,我也想知道继承的方法。 Using code below, I can come very, very close. 使用下面的代码,我可以非常非常接近。 But getting the correct return type of getE() eludes me. 但是获得getE()的正确返回类型是我的选择。 I can get the generic type "E" but not the actual type "java.lang.String": 我可以得到泛型类型“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"
            }
          }
        }
      }
    }
  }
}

The output is: 输出是:

java.lang.String
E

My question is, how do I find that "E" is actually "java.lang.String"? 我的问题是,我怎么发现“E”实际上是“java.lang.String”? I suspect it has something to do with a lookup into the typeArgs[], but I don't see how to get there. 我怀疑它与查找typeArgs []有关,但我不知道如何到达那里。 PLEASE do not respond unless you've actually worked with generics reflection. 请不要回应,除非你真的使用泛型反射。 I've seen a lot of posts with answers like "type erasure prevents this", which is not true. 我看过很多帖子都有“类型擦除可以防止这种情况”的答案,但事实并非如此。

I'd recommend using Guava for this. 我建议使用番石榴。 For example: 例如:

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

See TypeToken.resolveType(Type) . 请参见TypeToken.resolveType(Type)

The alternative is pretty complicated, but it's possible . 替代方案非常复杂,但它是可能的 You need to walk the hierarchy and map type variables to type arguments yourself. 您需要遍历层次结构并将类型变量映射到自己的类型参数。

Handling the most trivial case would be something like this: 处理最琐碎的案例将是这样的:

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

That sort of simplistic method will fail if you had something like this: 如果你有这样的东西,那种简单的方法将会失败:

abstract class AbstractDerived<T> extends Base<T> {}
class Derived extends AbstractDerived<String> {}

In cases like that you need to first map E from Base to T from AbstractDerived and then map T to String , so the method has to recurse or iterate the supertypes. 在这种情况下,您需要首先将EBase映射到从AbstractDerived映射到T ,然后将T映射到String ,因此该方法必须递归或迭代超类型。 I have a more complicated example of something like this here , but that example is still wrong for a number of reasons. 在这里有一个更复杂的例子,但由于种种原因,这个例子仍然是错误的。

Another hurdle you will run in to is that to return a new type with type variables replaced, you need to implement ParameterizedType , GenericArrayType and WildcardType yourself, or else use the undocumented sun.reflect classes. 您将遇到的另一个障碍是,要返回替换了类型变量的新类型,您需要自己实现ParameterizedTypeGenericArrayTypeWildcardType ,否则使用未记录的sun.reflect类。

All of that is to say you should really just use Guava which already handles that stuff, unless you're doing something like writing your own TypeToken for some reason. 所有这一切都是说你应该只使用已经处理过这些东西的Guava,除非你出于某种原因编写自己的TypeToken

The caveat to all of this, which it seems like you already know, is that all of this depends on an actual type argument being provided to the supertype in an extends clause (explicit or implicit as in an anonymous class). 您似乎已经知道,对所有这一点的警告是,所有这些都取决于在extends子句中提供给超类型的实际类型参数(在匿名类中显式或隐式)。 If you just do new Base<Double>() there's no way to recover the type argument at runtime. 如果您只是执行new Base<Double>()则无法在运行时恢复类型参数。

The trick is, getting the type parameters from the raw class, and correlating that with the type variables one gets when analyzing the return types or type arguments: 诀窍是,从原始类获取类型参数,并将其与分析返回类型或类型参数时获得的类型变量相关联:

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<TypeVariable,Type> 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());
              }
            }
          }
        }
      }
    }
  }
}

There is a mis-conception on your end. 你的结局是错误的概念。 The signature of getE() is defined in the base class already. getE()的签名已在类中定义。

Derived doesn't override or in any way modify that method. Derived不会覆盖或以任何方式修改该方法。 In other words: the method is defined to return E . 换句话说:该方法被定义为返回E The fact that you extend Base and give a specific type doesn't affect the definition of that method! 扩展Base并提供特定类型的事实不会影响该方法的定义!

Therefore your attempts to get to the "changed" return type are futile. 因此,您尝试进入“已更改”的返回类型是徒劳的。 Because the return type didn't change. 因为返回类型没有改变。 And for correlating - look here: 相关 - 请看这里:

    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());
        }
    }

The above prints: 以上打印:

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

(for my reworked example where I used two parameters E, F and had them replaced with String, Integer) (对于我重新编写的示例,我使用了两个参数E,F并将它们替换为String,Integer)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM