如何为Java类字段生成准确的泛型表达式? [英] How do I generate an accurate generics expression for a Java class field?

查看:64
本文介绍了如何为Java类字段生成准确的泛型表达式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图在运行时推断泛型.有很多很棒的库可以做到这一点(例如, gentyref Guava ).但是,它们的用法让我有些头疼.

I am trying to reason about generics at runtime. There are several great libraries to do this (e.g., gentyref, ClassMate and Guava). 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.

以下是使用gentyref的示例:

Here is an example using 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

我知道betterThingType Type对象(

I know the betterThingType Type object (a gentyref-specific implementation) is more sophisticated than what is shown by toString() here. But I am guessing I need to invoke getExactFieldType again with a non-wildcard Type to get what I'm looking for.

我的主要要求是我需要一个表达式,该表达式可以成为代码生成的源文件的一部分,该文件可以成功编译-或至少经过最少的修改即可编译.我愿意使用最适合该工作的任何库.

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.

推荐答案

要获取此类信息,您必须确定是否已向泛型类型参数提供了实际类型(例如Integer).如果没有,则需要获取所需类中已知的类型参数名称以及所有范围.

To get this kind of information, you must determine whether or not an actual type (e.g Integer) has been supplied to the generic type parameter. 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.

首先, Field方法返回所需的Type信息.如果提供一个实际的类作为类型,例如,Type可以是一个简单的Class. Integer thing;,也可以是TypeVariable,代表您在Thing中定义的通用类型参数,例如T thing;.

First, Field's getGenericType() method returns the Type information needed. Here, the Type can be a simple Class if an actual class is supplied as the type, e.g. Integer thing;, or it can be a TypeVariable, representing a generic type parameter as you have defined it in Thing, e.g. T thing;.

如果是通用类型,那么我们将需要了解以下内容:

If it's a generic type, then we will need to know the following:

  • 此类型最初在哪个类中声明.使用 FieldgetDeclaringClass方法.
  • 在每个子类中,从声明了Field的原始类中,在extends子句中提供了哪些类型的参数.这些类型参数本身可以是像Integer这样的实际类型,也可以是它们自己类的泛型类型参数.复杂的是,这些类型参数的名称可能不同,并且它们的声明顺序与超类中的顺序不同.可以通过调用 Class来检索extends子句数据.的getGenericSuperclass()方法,它返回的Type可以是简单的Class,例如Object,也可以是ParameterizedType,例如Thing<N>NumberThing<Integer>.
  • 可以使用 getTypeParameters()方法,该方法返回TypeVariable s的数组.
  • TypeVariable中可以提取名称,例如T及其边界,作为Type对象的数组,例如Number表示N extends Number.
  • In what class this type was originally declared. This is retrieved with Field's getDeclaringClass method.
  • In each subclass, from the original class that declared the Field, what type arguments were supplied in the extends clause. These type arguments may themselves be actual types like Integer, or they may be their own class's generic type parameters. 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, e.g Object, or it could be a ParameterizedType, e.g. Thing<N> or NumberThing<Integer>.
  • A class's own type parameters can be retrieved with Class's getTypeParameters() method, which returns an array of TypeVariables.
  • From a TypeVariable you can extract the name, e.g. T, and the bounds, as an array of Type objects, e.g. Number for N extends Number.

对于泛型类型参数,我们需要在类层次结构中一直跟踪哪些子类类型参数与原始泛型类型参数匹配,直到到达原始的Class为止,在其中我们报告泛型类型参数以及任何边界,或者我们到达一个实际的Class对象,并在其中报告该类.

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.

这是一个基于您的课程的程序,该程序报告您所需的信息.

Here is a program, based on your classes, that reports your desired information.

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

It must create a Stack of Classes, going from the original class up to the class that declares the field. 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. E.g. T becomes N extends Number when going from Thing to NumberThing. The loop iterations stop when the type argument is an actual class, e.g. Integer, or if we've reached the original class, in which case we report the type parameter name and any bounds, e.g. N extends Number.

我还包括了两个附加类SuperclassSubclass,其中Subclass颠倒了Superclass中声明的泛型类型参数的顺序,以提供其他测试.我还包含了SpecificIntegerThing(非泛型)作为测试用例,以便迭代在IntegerThing处停止,以在Integer到达堆栈中的SpecificIntegerThing之前进行报告.

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

// 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方法有助于生成类型参数名称和任何边界.

The getTypeVariableString method helps to generate the type parameter name and any bounds.

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

这篇关于如何为Java类字段生成准确的泛型表达式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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