有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

我试图在运行时对泛型进行推理。有几个很棒的库可以做到这一点(例如,gentyrefClassMateGuava)。然而,它们的用法有点让我摸不着头脑

具体来说,我想提取一个表达式,它与子类上下文中的特定字段相匹配

下面是一个使用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");
  }

}

以下是输出:

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

我知道betterThingTypeType对象(a gentyref-specific implementation)比这里的toString()显示的更复杂。但我猜我需要再次用非通配符getExactFieldType调用Type来获得我想要的

我的主要要求是,我需要一个表达式,它可以成为代码生成的源文件的一部分,该源文件可以成功编译,或者至少只需很少的修改就可以编译。我愿意使用最适合这份工作的图书馆


共 (1) 个答案

  1. # 1 楼答案

    要获取此类信息,必须确定是否已向泛型类型参数提供了实际类型(例如Integer)。如果没有,您将需要获取类型参数名,正如您所需的类中已知的那样,以及任何边界

    事实证明这相当复杂。但首先,让我们回顾一下我们将在解决方案中使用的一些反射技术和方法

    首先,^{}'s ^{} method返回所需的Type信息。这里,Type可以是一个简单的Class,如果实际类作为类型提供,例如Integer thing;,或者它可以是一个TypeVariable,表示在Thing中定义的泛型类型参数,例如T thing;

    如果是泛型,那么我们需要了解以下内容:

    • 这个类型最初是在哪个类中声明的。这是用^{}'s ^{} method检索的
    • 在每个子类中,从声明Field的原始类开始,extends子句中提供了什么类型的参数。这些类型参数本身可能是Integer之类的实际类型,也可能是它们自己的类的泛型类型参数。更复杂的是,这些类型参数的名称可能不同,并且它们的声明顺序可能与在超类中不同。可以通过调用^{}'s ^{} method来检索extends子句数据,它返回一个Type,可以是简单的Class,例如Object,也可以是ParameterizedType,例如Thing<N>NumberThing<Integer>
    • 类自己的类型参数可以用^{}'s ^{} method检索,它返回一个TypeVariable数组
    • TypeVariable中,可以提取名称,例如T,以及边界,作为Type对象的数组,例如Number用于N extends Number

    对于泛型类型参数,我们需要跟踪哪些子类类型参数与原始泛型类型参数匹配,直到我们到达原始的Class,我们在其中报告具有任何边界的泛型类型参数,或者到达实际的Class对象,我们在其中报告类

    这是一个基于你的课程的程序,可以报告你想要的信息

    它必须创建一个StackClass的类,从原始类一直到声明该字段的类。然后它弹出类,沿着类层次结构走。它会在当前类中查找与上一个类中的类型参数匹配的类型参数,并记录所有类型参数名称的更改以及当前类提供的新类型参数的新位置。例如,从ThingNumberThingT变成N extends Number。当类型参数是实际类时,循环迭代停止,例如Integer,或者如果我们已经到达原始类,在这种情况下,我们报告类型参数名称和任何边界,例如N extends Number

    我还包括了两个额外的类SuperclassSubclass,其中Subclass反转Superclass中声明的泛型类型参数的顺序,以提供额外的测试。我还包括了SpecificIntegerThing(非泛型),作为测试用例,以便迭代在IntegerThing停止,在到达堆栈中的SpecificIntegerThing之前报告Integer

    // 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() + "!");
             }
          }
       }
    

    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!");
       }
    

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

    这会影响输出:

    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