简体   繁体   English

如何为Java类字段生成准确的泛型表达式?

[英]How do I generate an accurate generics expression for a Java class field?

I am trying to reason about generics at runtime. 我试图在运行时推理泛型。 There are several great libraries to do this (eg, gentyref , ClassMate and Guava ). 有几个很棒的库(例如gentyrefClassMateGuava )。 However, their usage is a little over my head. 但是,它们的用法有点过头了。

Specifically, I want to extract an expression which matches a particular field in the context of a subclass. 具体来说,我想提取一个与子类上下文中的特定字段匹配的表达式。

Here is an example using gentyref: 以下是使用gentyref的示例:

import com.googlecode.gentyref.GenericTypeReflector;

import java.lang.reflect.Field;
import java.lang.reflect.Type;

public class ExtractArguments {

  public static class Thing<T> {
    public T thing;
  }

  public static class NumberThing<N extends Number> extends Thing<N> { }

  public static class IntegerThing extends NumberThing<Integer> { }

  public static void main(final String... args) throws Exception {
    final Field thing = Thing.class.getField("thing");

    // naive type without context
    Class<?> thingClass = thing.getType(); // Object
    System.out.println("thing class = " + thingClass);
    Type thingType = thing.getGenericType(); // T
    System.out.println("thing type = " + thingType);
    System.out.println();

    // exact types without adding wildcard
    Type exactThingType = GenericTypeReflector.getExactFieldType(thing, Thing.class);
    System.out.println("exact thing type = " + exactThingType);
    Type exactNumberType = GenericTypeReflector.getExactFieldType(thing, NumberThing.class);
    System.out.println("exact number type = " + exactNumberType);
    Type exactIntegerType = GenericTypeReflector.getExactFieldType(thing, IntegerThing.class);
    System.out.println("exact integer type = " + exactIntegerType);
    System.out.println();

    // exact type with wildcard
    final Type wildThingType = GenericTypeReflector.addWildcardParameters(Thing.class);
    final Type betterThingType = GenericTypeReflector.getExactFieldType(thing, wildThingType);
    System.out.println("better thing type = " + betterThingType);
    final Type wildNumberType = GenericTypeReflector.addWildcardParameters(NumberThing.class);
    final Type betterNumberType = GenericTypeReflector.getExactFieldType(thing, wildNumberType);
    System.out.println("better number type = " + betterNumberType);
    final Type wildIntegerType = GenericTypeReflector.addWildcardParameters(IntegerThing.class);
    final Type betterIntegerType = GenericTypeReflector.getExactFieldType(thing, wildIntegerType);
    System.out.println("better integer type = " + betterIntegerType);
    System.out.println();

    System.out.println("desired thing type = T");
    System.out.println("desired number thing type = N extends Number");
    System.out.println("desired integer thing type = Integer");
  }

}

And here is the output: 这是输出:

thing class = class java.lang.Object
thing type = T

exact thing type = class java.lang.Object
exact number type = class java.lang.Object
exact integer type = class java.lang.Integer

better thing type = capture of ?
better number type = capture of ?
better integer type = class java.lang.Integer

desired thing type = T
desired number thing type = N extends Number
desired integer thing type = Integer

I know the betterThingType Type object (a gentyref-specific implementation ) is more sophisticated than what is shown by toString() here. 我知道betterThingType Type对象( gentyref特定的实现 )比toString()在这里显示的更复杂。 But I am guessing I need to invoke getExactFieldType again with a non-wildcard Type to get what I'm looking for. 但我猜我需要再次使用非通配符Type调用getExactFieldType来获取我正在寻找的内容。

My main requirement is that I need an expression which could become part of a code-generated source file that could be successfully compiled—or at least compiled with minimal modification. 我的主要要求是我需要一个表达式,该表达式可以成为代码生成的源文件的一部分,可以成功编译 - 或者至少可以通过最少的修改进行编译。 I am open to using whatever library is best for the job. 我愿意使用任何最适合工作的库。

To get this kind of information, you must determine whether or not an actual type (eg Integer ) has been supplied to the generic type parameter. 要获得此类信息,必须确定是否已向泛型类型参数提供实际类型(例如, Integer )。 If not, you will need to get the type parameter name, as it's known in the class you need, along with any bounds. 如果没有,您将需要获取类型参数名称,因为它在您需要的类中已知,以及任何边界。

This turns out to be quite complicated. 事实证明这很复杂。 But first, let's go over some of the reflection techniques and methods we'll use in the solution. 但首先,让我们回顾一下我们将在解决方案中使用的一些反射技术和方法。

First, Field 's getGenericType() method returns the Type information needed. 首先, FieldgetGenericType()方法返回所需的Type信息。 Here, the Type can be a simple Class if an actual class is supplied as the type, eg Integer thing; 这里,如果提供一个实际的类作为类型, Type可以是一个简单的Class ,例如Integer thing; , or it can be a TypeVariable , representing a generic type parameter as you have defined it in Thing , eg T thing; ,或者它可以是TypeVariable ,表示你在Thing定义的泛型类型参数,例如T thing; .

If it's a generic type, then we will need to know the following: 如果它是泛型类型,那么我们需要知道以下内容:

  • In what class this type was originally declared. 在最初宣布这种类型的类中。 This is retrieved with Field 's getDeclaringClass method . 这是使用FieldgetDeclaringClass方法检索的。
  • In each subclass, from the original class that declared the Field , what type arguments were supplied in the extends clause. 在每个子类中,从声明Field的原始类中,在extends子句中提供了哪些类型的参数。 These type arguments may themselves be actual types like Integer , or they may be their own class's generic type parameters. 这些类型参数本身可能是像Integer这样的实际类型,或者它们可能是它们自己的类的泛型类型参数。 Complicating matters, these type parameters may be differently named, and they could be declared in a different order than in the superclass. 更复杂的是,这些类型参数可能有不同的名称,并且它们可以以与超类中不同的顺序声明。 The extends clause data can be retrieved by calling Class 's getGenericSuperclass() method , which returns a Type that can be a simple Class , eg Object , or it could be a ParameterizedType , eg Thing<N> or NumberThing<Integer> . 可以通过调用ClassgetGenericSuperclass()方法来检索extends子句数据,该方法返回一个可以是简单ClassType ,例如Object ,或者它可以是ParameterizedType ,例如Thing<N>NumberThing<Integer>
  • A class's own type parameters can be retrieved with Class 's getTypeParameters() method , which returns an array of TypeVariable s. 可以使用ClassgetTypeParameters()方法检索类自己的类型参数,该方法返回TypeVariable的数组。
  • From a TypeVariable you can extract the name, eg T , and the bounds, as an array of Type objects, eg Number for N extends Number . TypeVariable您可以提取名称,例如T和边界,作为Type对象的数组,例如Number for N extends Number

For the generic type parameter, we need to track which subclass type arguments match the original generic type parameter, down through the class hierarchy, until we either reach the original Class , in which we report the generic type parameter with any bounds, or we reach an actual Class object, in which we report the class. 对于泛型类型参数,我们需要跟踪哪些子类型参数与原始泛型类型参数匹配,直到类层次结构,直到我们到达原始Class ,在Class中我们报告具有任何边界的泛型类型参数,或者我们到达一个实际的Class对象,我们在其中报告该类。

Here is a program, based on your classes, that reports your desired information. 这是一个基于您的课程的程序,用于报告您所需的信息。

It must create a Stack of Class es, going from the original class up to the class that declares the field. 它必须创建一个Stack of Class es,从原始类到声明该字段的类。 Then it pops the classes, walking down the class hierarchy. 然后它会弹出类,沿着类层次结构向下移动。 It finds the type argument in the current class that matches the type parameter from the previous class, noting down any type parameter name changes and new positions of the new type argument provided by the current class. 它在当前类中找到与前一个类中的type参数匹配的type参数,记下当前类提供的任何类型参数名称更改和新类型参数的新位置。 Eg T becomes N extends Number when going from Thing to NumberThing . 例如,当从Thing转到NumberThing时, T变为N extends Number NumberThing The loop iterations stop when the type argument is an actual class, eg Integer , or if we've reached the original class, in which case we report the type parameter name and any bounds, eg N extends Number . 当类型参数是实际类(例如Integer ,或者如果我们已经到达原始类,循环迭代停止,在这种情况下,我们报告类型参数名称和任何边界,例如N extends Number

I have also included a couple additional classes, Superclass and Subclass , where Subclass reverses the order of generic type arguments that are declared in Superclass , to provide additional testing. 我还包括了一些额外的类, SuperclassSubclass ,其中Subclass颠倒了在Superclass中声明的泛型类型参数的顺序,以提供额外的测试。 I also included SpecificIntegerThing (non-generic), as a test case so that the iteration stops at IntegerThing , to report Integer , before it reaches SpecificIntegerThing in the stack. 我还包括了SpecificIntegerThing (非泛型),作为测试用例,以便迭代在IntegerThing停止,报告Integer ,然后到达堆栈中的SpecificIntegerThing

// Just to have some bounds to report.
import java.io.Serializable;
import java.util.RandomAccess;

// Needed for the implementation.
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.Stack;

public class ExtractArguments {

   public static class Thing<T> {
      public T   thing;
   }

   public static class NumberThing<N extends Number> extends Thing<N> {}

   public static class IntegerThing extends NumberThing<Integer> {}

   public static class SpecificIntegerThing extends IntegerThing {}

   public static class Superclass<A extends Serializable, B> {
      public A thing;
   }

   // A and B are reversed in the extends clause!
   public static class Subclass<A, B extends RandomAccess & Serializable>
      extends Superclass<B, A> {}  

   public static void main(String[] args)
   {
      for (Class<?> clazz : Arrays.asList(
              Thing.class, NumberThing.class,
              IntegerThing.class, SpecificIntegerThing.class,
              Superclass.class, Subclass.class))
      {
         try
         {
            Field field = clazz.getField("thing");
            System.out.println("Field " + field.getName() + " of class " + clazz.getName() + " is: " +
                    getFieldTypeInformation(clazz, field));
         }
         catch (NoSuchFieldException e)
         {
            System.out.println("Field \"thing\" is not found in class " + clazz.getName() + "!");
         }
      }
   }

The getFieldTypeInformation method does the work with the stack. getFieldTypeInformation方法可以处理堆栈。

   private static String getFieldTypeInformation(Class<?> clazz, Field field)
   {
      Type genericType = field.getGenericType();
      // Declared as actual type name...
      if (genericType instanceof Class)
      {
         Class<?> genericTypeClass = (Class<?>) genericType;
         return genericTypeClass.getName();
      }
      // .. or as a generic type?
      else if (genericType instanceof TypeVariable)
      {
         TypeVariable<?> typeVariable = (TypeVariable<?>) genericType;
         Class<?> declaringClass = field.getDeclaringClass();
         //System.out.println(declaringClass.getName() + "." + typeVariable.getName());

         // Create a Stack of classes going from clazz up to, but not including, the declaring class.
         Stack<Class<?>> stack = new Stack<Class<?>>();
         Class<?> currClass = clazz;
         while (!currClass.equals(declaringClass))
         {
            stack.push(currClass);
            currClass = currClass.getSuperclass();
         }
         // Get the original type parameter from the declaring class.
         int typeVariableIndex = -1;
         String typeVariableName = typeVariable.getName();
         TypeVariable<?>[] currTypeParameters = currClass.getTypeParameters();
         for (int i = 0; i < currTypeParameters.length; i++)
         {
            TypeVariable<?> currTypeVariable = currTypeParameters[i];
            if (currTypeVariable.getName().equals(typeVariableName))
            {
               typeVariableIndex = i;
               break;
            }
         }

         if (typeVariableIndex == -1)
         {
            throw new RuntimeException("Expected Type variable \"" + typeVariable.getName() +
                    "\" in class " + clazz + "; but it was not found.");
         }

         // If the type parameter is from the same class, don't bother walking down
         // a non-existent hierarchy.
         if (declaringClass.equals(clazz))
         {
            return getTypeVariableString(typeVariable);
         }

         // Pop them in order, keeping track of which index is the type variable.
         while (!stack.isEmpty())
         {
            currClass = stack.pop();
            // Must be ParameterizedType, not Class, because type arguments must be
            // supplied to the generic superclass.
            ParameterizedType superclassParameterizedType = (ParameterizedType) currClass.getGenericSuperclass();
            Type currType = superclassParameterizedType.getActualTypeArguments()[typeVariableIndex];
            if (currType instanceof Class)
            {
               // Type argument is an actual Class, e.g. "extends ArrayList<Integer>".
               currClass = (Class) currType;
               return currClass.getName();
            }
            else if (currType instanceof TypeVariable)
            {
               TypeVariable<?> currTypeVariable = (TypeVariable<?>) currType;
               typeVariableName = currTypeVariable.getName();
               // Reached passed-in class (bottom of hierarchy)?  Report it.
               if (currClass.equals(clazz))
               {
                  return getTypeVariableString(currTypeVariable);
               }
               // Not at bottom?  Find the type parameter to set up for next loop.
               else
               {
                  typeVariableIndex = -1;
                  currTypeParameters = currClass.getTypeParameters();
                  for (int i = 0; i < currTypeParameters.length; i++)
                  {
                     currTypeVariable = currTypeParameters[i];
                     if (currTypeVariable.getName().equals(typeVariableName))
                     {
                        typeVariableIndex = i;
                        break;
                     }
                  }

                  if (typeVariableIndex == -1)
                  {
                     // Shouldn't get here.
                     throw new RuntimeException("Expected Type variable \"" + typeVariable.getName() +
                         "\" in class " + currClass.getName() + "; but it was not found.");
                  }
               }
            }
         }
      }
      // Shouldn't get here.
      throw new RuntimeException("Missed the original class somehow!");
   }

The getTypeVariableString method helps to generate the type parameter name and any bounds. getTypeVariableString方法有助于生成类型参数名称和任何边界。

   // Helper method to print a generic type parameter and its bounds.
   private static String getTypeVariableString(TypeVariable<?> typeVariable)
   {
      StringBuilder buf = new StringBuilder();
      buf.append(typeVariable.getName());
      Type[] bounds = typeVariable.getBounds();
      boolean first = true;
      // Don't report explicit "extends Object"
      if (bounds.length == 1 && bounds[0].equals(Object.class))
      {
         return buf.toString();
      }
      for (Type bound : bounds)
      {
         if (first)
         {
            buf.append(" extends ");
            first = false;
         }
         else
         {
            buf.append(" & ");
         }
         if (bound instanceof Class)
         {
            Class<?> boundClass = (Class) bound;
            buf.append(boundClass.getName());
         }
         else if (bound instanceof TypeVariable)
         {
            TypeVariable<?> typeVariableBound = (TypeVariable<?>) bound;
            buf.append(typeVariableBound.getName());
         }
      }
      return buf.toString();
   }
}

This the output: 这个输出:

Field thing of class ExtractArguments$Thing is: T
Field thing of class ExtractArguments$NumberThing is: N extends java.lang.Number
Field thing of class ExtractArguments$IntegerThing is: java.lang.Integer
Field thing of class ExtractArguments$SpecificIntegerThing is: java.lang.Integer
Field thing of class ExtractArguments$Superclass is: A extends java.io.Serializable
Field thing of class ExtractArguments$Subclass is: B extends java.util.RandomAccess & java.io.Serializable

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

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