【问题标题】:Retrieving type parameters from an instance of a generic base interface从泛型基接口的实例中检索类型参数
【发布时间】:2009-02-17 16:37:47
【问题描述】:

给定 2 个接口:

public interface BaseInterface<T> { }
public interface ExtendedInterface<T0, T1> extends BaseInterface<T0> {}

还有一个具体的类:

public class MyClass implements ExtendedInterface<String, Object> { }

如何找出传递给 BaseInterface 接口的类型参数?

(我可以通过调用类似的方法来检索 ExtendedInterface 类型参数

MyClass.class.getGenericInterfaces()[0].getActualTypeArguments()

但我找不到一种简单的方法来递归到任何基本的通用接口并得到任何有意义的东西)。

【问题讨论】:

    标签: java generics reflection


    【解决方案1】:

    这个问题一般不容易完全解决。例如,如果它是一个内部类,你还必须考虑包含类的类型参数,...

    因为仅使用 Java 本身提供的反射泛型类型非常困难,所以我编写了一个库来完成这项艰巨的工作:gentyref。见http://code.google.com/p/gentyref/ 对于您的示例,使用 gentyref,您可以这样做:

    Type myType = MyClass.class;
    
    // get the parameterized type, recursively resolving type parameters
    Type baseType = GenericTypeReflector.getExactSuperType(myType, BaseInterface.class);
    
    if (baseType instanceof Class<?>) {
        // raw class, type parameters not known
        // ...
    } else {
        ParameterizedType pBaseType = (ParameterizedType)baseType;
        assert pBaseType.getRawType() == BaseInterface.class; // always true
        Type typeParameterForBaseInterface = pBaseType.getActualTypeArguments()[0];
        System.out.println(typeParameterForBaseInterface);
    }
    

    【讨论】:

      【解决方案2】:

      我不知道你到底想实现什么,知道什么,不知道什么,但你可以像这样递归到超级接口:

      Type[] interfaces = MyClass.class.getGenericInterfaces();
      
      ParameterizedType extInterfaceType = (ParameterizedType)interfaces[0];
      Class<?> extInterfaceClass = (Class<?>)extInterfaceType.getRawType();
      
      Type[] baseInterfaces = extInterfaceClass.getGenericInterfaces();
      ParameterizedType baseInterfaceType = (ParameterizedType)baseInterfaces[0];
      Class<?> baseInterfaceClass = (Class<?>)baseInterfaceType.getRawType();
      

      当然,如果您以这种方式达到第二级,则只能将您的名称 T0 和 T1 作为通用参数。如果您知道ExtendedInterfaceBaseInterface 之间的关系,那么您实际上不必走那么远,因为您知道将前者的哪个通用参数传递给后者。如果没有,您可能必须遍历它们的参数并找到匹配项。基于此的东西可能:

      Type[] params = extInterfaceClass.getTypeParameters();
      for (Type param : params) {
          if (param == baseInterfaceType.getActualTypeArguments()[0]) {
              // ...
          }
      }
      

      【讨论】:

        【解决方案3】:

        这很难使用 Java 反射 API 解决,因为需要解析所有遇到的类型变量。 Guava 从版本 12 开始具有 TypeToken 类,其中包含完全解析的类型信息。

        对于你的例子,你可以这样做:

        TypeToken<? extends T> token = TypeToken.of(MyClass.class);
        ParameterizedType type =
            (ParameterizedType) token.getSupertype(BaseInterface.class).getType();
        Type[] parameters = type.getActualTypeArguments();
        

        您仍然需要记住,这仅适用于 MyClass 本身不是泛型的情况。否则,由于类型擦除,类型参数的值在运行时不可用。

        【讨论】:

          【解决方案4】:

          我认为没有直接方法可以获取基本接口的泛型类型。

          一种方法是在接口中声明一个方法,如下所示:

          public interface BaseInterface<T> {
              Class<T> getGenericClass();
          }
          

          另外,我不知道你对这些课程有什么样的控制权。您始终可以断言所有实现者都具有显式声明的基本接口,如下所示:

          public class MyClass implements ExtendedInterface<String, Object>, BaseInterface<String>{ }
          

          MyClass.class.getGenericInterfaces()[1].getActualTypeArguments()[0]
          

          【讨论】:

            【解决方案5】:

            有点做了你所追求的,但它仍然不对。例如,它不处理 Foo&lt;T&gt; implements Bar&lt;Map&lt;T&gt;&gt; 的情况。你真正需要的是某种方式来询问 jvm “好的,这是一个类型列表。如果我将这些应用于这个泛型类型,我会得到什么实际类型?”

            但是,这段代码有点像你想要的。

            import java.lang.reflect.GenericDeclaration;
            import java.lang.reflect.ParameterizedType;
            import java.util.*;
            
            interface BaseInterface<T> {}
            interface FirstArg<T1,T2> extends BaseInterface<T1>{}
            interface SecondArg<T1,T2> extends BaseInterface<T2>{}
            
            class First implements FirstArg<Number, String> {}
            class Second implements SecondArg<Number, String> {}
            
            
            public class Example {
                public static void main(String[] av) {
                    new Example().go();
                }
            
                void go() {
                    test(First.class);
                    test(Second.class);
                }
            
                void test(Class<?> c1) {        
                    ParameterizedType t2 = (ParameterizedType) c1.getGenericInterfaces()[0];
                    System.out.println(c1 + " implements " + t2 );
            
                    Class<?> c2 = (Class<?>)t2.getRawType();
                    GenericDeclaration g2 = (GenericDeclaration) c2;
            
                    System.out.println(t2 + "  params are " + Arrays.asList(g2.getTypeParameters()));
            
                    System.out.println("So that means");
                    for(int i = 0; i<t2.getActualTypeArguments().length; i++) {
                        System.out.println("Parameter " + c2.getTypeParameters()[i] + " is " + t2.getActualTypeArguments()[i]);
                    }
            
                    ParameterizedType t3 =  (ParameterizedType) c2.getGenericInterfaces()[0];
                    System.out.println(t2 + "  implements " + t3);
            
                    System.out.println("and so that means we are talking about\n" + t3.getRawType().toString() + " <");
                    for(int i = 0 ; i< t3.getActualTypeArguments().length; i++) {
                        System.out.println("\t" + t3.getActualTypeArguments()[i] + " -> " 
                        + Arrays.asList(g2.getTypeParameters()).indexOf(t3.getActualTypeArguments()[i])
                        + " -> " + 
                        t2.getActualTypeArguments()[Arrays.asList(g2.getTypeParameters()).indexOf(t3.getActualTypeArguments()[i])]
                        );
                    }
            
                    System.out.println(">");
                    System.out.println();
                }
            
            }
            

            【讨论】:

            • 这也是我实现它的方式。不过,我使用了类型变量名称,因为可能无法保证参数顺序(我认为)(在我的示例中,ExtendedInterface 可以将 T1 传递给基本接口)。
            • 嗯,这正是我的代码处理的 - 你会注意到两个接口,一个传递参数 1,一个传递参数 2。
            【解决方案6】:

            我不认为你可以,因为这些是特定于实例而不是特定于类的。考虑以下几点:

            List<String>  a = new ArrayList<String>();
            

            a 是字符串的通用列表这一事实是特定于实例 a 而不是类 List 的。因此,List.class 对象的任何方法都不能告诉您泛化类型将是 a 的 String 类型。尽管您的示例中的 MyClass 恰好为接口的通用类型设置了值,但我认为这在接口 Class 对象实例中不可用。

            【讨论】:

            • 可以从参数化类型的实例中检索类型参数。在您的示例中,可以挖掘 a.getClass() 以检索传递给 ArrayList 的类型 String。
            • 是的,抱歉,我的评论是线轴(深夜脑放屁)。当然,我的意思是,如果你有一个围绕 ArrayList 的非泛型包装类,那么你就可以做到。正确的?哎呀。
            【解决方案7】:

            我认为我能想到的唯一选择是检查由BaseInterface 声明的泛型方法,而不是被覆盖。

            【讨论】:

              【解决方案8】:

              我回答我自己的问题又是不礼貌的行为。

              正如 gix 指出的,当您开始遍历泛型类型的层次结构时,除了第一个之外,您会丢失有关类型参数的信息。

              但重要的一点是:您获得了第一个要实例化的通用接口的类型参数(在我的示例中,ExtendedInterface),您还获得了用于创建子接口的类型参数的名称。

              因此,可以通过将 TypeVariable 名称映射到实际类型参数来确定基本接口的类型参数。

              稍后我会更新一些代码,但它确实有效(您可以从 MyClass.class 中确定用于实例 BaseInterface 的类型参数)。

              更新 这是第一次通过,为一些简单的单元测试开了绿灯。它需要工作......真正的问题是,这个问题值得这样荒谬的解决方案吗?

              public class GenericReflectionUtils
              {
              @SuppressWarnings("unchecked")
              public static List<Class> getGenericInterfaceTypeArguments(Class baseInterface, Class concreteClass)
              {
                  if (!baseInterface.isAssignableFrom(concreteClass))
                  {
                      throw new IllegalArgumentException("Illegal base interface argument");
                  }
                  if (concreteClass.getTypeParameters().length > 0)
                  {
                      throw new IllegalArgumentException("Can't determine the type arguments of a generic interface of a generic class");
                  }
                  for (Type genericInterface : concreteClass.getGenericInterfaces())
                  {
                      List<Class> result = null;
                      if (genericInterface instanceof Class)
                      {
                          result = getGenericInterfaceTypeArguments(baseInterface,(Class)genericInterface);
                      }
                      else
                      {
                          result = getGenericInterfaceTypeArguments(baseInterface, (ParameterizedType)genericInterface);
                      }
                      if (result != null)
                      {
                          return result;
                      }
                  }
                  return null;
              }
              
              
              public static Class getClass(Type type)
              {
                  if (type instanceof Class)
                  {
                      return (Class) type;
                  }
                  if (type instanceof ParameterizedType)
                  {
                      return getClass(((ParameterizedType) type).getRawType());
                  }
                  if (type instanceof GenericArrayType)
                  {
                      Type componentType = ((GenericArrayType) type).getGenericComponentType();
                      Class<?> componentClass = getClass(componentType);
                      if (componentClass != null)
                      {
                          return Array.newInstance(componentClass, 0).getClass();
                      }
                      return null;
                  }
                  return null;
              }
              
              @SuppressWarnings("unchecked")
              private static List<Class> getGenericInterfaceTypeArguments(Class baseInterface, ParameterizedType currentType)
              {
                  Class currentClass = getClass(currentType);
                  if (!baseInterface.isAssignableFrom(currentClass))
                  {
                      //  Early out - current type is not an interface that extends baseInterface
                      return null;
                  }
              
                  Type[] actualTypeArguments = currentType.getActualTypeArguments();
              
                  if (currentClass == baseInterface)
                  {
                      //  currentType is a type instance of the base generic interface. Read out the type arguments and return 
                      ArrayList<Class> typeArgs = new ArrayList<Class>(actualTypeArguments.length);
                      for (Type typeArg : actualTypeArguments)
                      {
                          typeArgs.add(getClass(typeArg));
                      }
              
                      return typeArgs;
                  }
              
                  //  currentType is derived
                  Map<String, Class> typeVarMap = createTypeParameterMap(currentType, null);
              
                  for (Type genericInterfaceType : currentClass.getGenericInterfaces())
                  {
                      List<Class> result = getGenericInterfaceTypeArguments(baseInterface, (ParameterizedType)genericInterfaceType, typeVarMap);
                      if (result != null)
                      {
                          return result;
                      }
                  }
                  return null;
              }
              
              private static Map<String, Class> createTypeParameterMap(ParameterizedType type, Map<String, Class> extendedTypeMap)
              {
                  Map<String, Class> typeVarMap = new HashMap<String, Class>();
                  Type[] typeArgs = type.getActualTypeArguments();
                  TypeVariable[] typeVars = getClass(type).getTypeParameters();
                  for (int typeArgIndex = 0; typeArgIndex < typeArgs.length; ++typeArgIndex)
                  {
                      //  Does not deal with nested generic arguments...
                      Type typeArg = typeArgs[typeArgIndex];
                      if (typeArg instanceof TypeVariable)
                      {
                          assert extendedTypeMap != null;
                          TypeVariable typeVar = (TypeVariable)typeArg;
                          typeVarMap.put(typeVars[typeArgIndex].getName(), extendedTypeMap.get(typeVar.getName()));
                          continue;
                      }
                      typeVarMap.put(typeVars[typeArgIndex].getName(), getClass(typeArgs[typeArgIndex]));
                  }
              
                  return typeVarMap;
              }
              
              private static List<Class> createTypeParameterList(Map<String, Class> typeParameterMap, ParameterizedType type)
              {
                  ArrayList<Class> typeParameters = new ArrayList<Class>(typeParameterMap.size());
                  for (Type actualType : type.getActualTypeArguments())
                  {
                      if (actualType instanceof TypeVariable)
                      {
                          //  Handles the case when an interface is created with a specific type, rather than a parameter
                          typeParameters.add(typeParameterMap.get(((TypeVariable)actualType).getName()));
                          continue;
                      }
                      typeParameters.add(getClass(actualType));
                  }
                  return typeParameters;
              }
              
              @SuppressWarnings("unchecked")
              private static List<Class> getGenericInterfaceTypeArguments(Class baseInterface, ParameterizedType currentType, Map<String, Class> currentTypeParameters)
              {
                  Class currentClass = getClass(currentType);
                  if (!baseInterface.isAssignableFrom(currentClass))
                  {
                      //  Early out - current type is not an interface that extends baseInterface
                      return null;
                  }
              
                  if (currentClass == baseInterface)
                  {
                      return createTypeParameterList(currentTypeParameters, currentType);
                  }
              
                  currentTypeParameters = createTypeParameterMap(currentType, currentTypeParameters);
                  for (Type genericInterface : currentClass.getGenericInterfaces())
                  {
                      List<Class> result = getGenericInterfaceTypeArguments(baseInterface, (ParameterizedType)genericInterface, currentTypeParameters);
                      if (result != null)
                      {
                          return result;
                      }
                  }
              
                  return null;
              }
              

              }

              【讨论】:

              • 这看起来确实是一个好的开始,类似于 gentyref 所做的。它仍然需要一些工作才能完成。想想嵌套类、嵌套泛型,然后你会注意到在接口 I 实现 BaseInterface>,...
              • 非常好,谢谢Wouter。稍后我将使用更完整的解决方案对此进行更新。目前,它适合我在一个截止日期紧迫的项目中的目的,所以我暂时搁置它。
              【解决方案9】:
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2018-05-26
              • 2015-08-25
              • 1970-01-01
              • 2013-08-16
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多