Java反射:获得正确类型的参数化泛型超类方法 [英] Java reflection: getting correct type of parameterized generic superclass method

查看:140
本文介绍了Java反射:获得正确类型的参数化泛型超类方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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







$ b

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

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

  package net.redpoint 。刮; 
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);
类型superType = clazz.getGenericSuperclass();
if(superType instanceof ParameterizedType){
ParameterizedType superPt =(ParameterizedType)superType;
类型[] typeArgs = superPt.getActualTypeArguments();
类型t0 = typeArgs [0];
//这是java.lang.String
System.out.println(t0.getTypeName());
类型superRawType = superPt.getRawType();
if(superRawType instanceof Class){
Class superRawClazz =(Class)superRawType; (方法method:superRawClazz.getDeclaredMethods()){
if(method.getName()。equals(getE)){
类型returnType = method.getGenericReturnType();
if(returnType instanceof TypeVariable){
TypeVariable tv =(TypeVariable)returnType;
//这是E
System.out.println(tv.getName());
//如何将这个E返回到正确类型java.lang.String
}
}
}
}
}
}
}

输出结果为:

  java.lang.String 
E

我的问题是,我如何发现E实际上是java.lang.String?我怀疑它与查找typeArgs []有关,但我不知道如何到达那里。除非你真正使用泛型反射,否则请不要回复。我看到很多帖子的答案是type erasure prevent this,这是不正确的。

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

 类型returnType = 
新TypeToken< Derived>(){}
.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 ]。
}
}
返回null;





$ b

如果你有这样的事情,那么简单的方法就会失败: p>

 抽象类AbstractDerived< T>延伸Base< T> {} 
类Derived extends AbstractDerived< String> {}

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



另一个障碍是要返回一个替换了类型变量的新类型,您需要实现 ParameterizedType GenericArrayType WildcardType 自己,或者使用未记录的 sun.reflect b
$ b

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



对于所有这些,你应该已经知道,这取决于在 extends 子句中提供给超类型的实际类型参数(在匿名类中显式或隐式)。如果您仅执行新的Base< Double>(),则无法在运行时恢复类型参数。


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). 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. I can get the generic type "E" but not the actual type "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"? I suspect it has something to do with a lookup into the typeArgs[], but I don't see how to get there. 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).

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. 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.

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.

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). If you just do new Base<Double>() there's no way to recover the type argument at runtime.

这篇关于Java反射:获得正确类型的参数化泛型超类方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆