如何为Java类字段生成准确的泛型表达式? [英] How do I generate an accurate generics expression for a Java class field?
问题描述
我试图在运行时推断泛型.有很多很棒的库可以做到这一点(例如, 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
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:
- 此类型最初在哪个类中声明.使用
Field
的getDeclaringClass
方法. - 在每个子类中,从声明了
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
'sgetDeclaringClass
method. - In each subclass, from the original class that declared the
Field
, what type arguments were supplied in theextends
clause. These type arguments may themselves be actual types likeInteger
, 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. Theextends
clause data can be retrieved by callingClass
'sgetGenericSuperclass()
method, which returns aType
that can be a simpleClass
, e.gObject
, or it could be aParameterizedType
, e.g.Thing<N>
orNumberThing<Integer>
. - A class's own type parameters can be retrieved with
Class
'sgetTypeParameters()
method, which returns an array ofTypeVariable
s. - From a
TypeVariable
you can extract the name, e.g.T
, and the bounds, as an array ofType
objects, e.g.Number
forN 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.
它必须创建一个Stack
个Class
es,从原始类一直到声明该字段的类.然后它弹出类,沿着类层次结构走.它会在当前类中找到与上一类的类型参数匹配的类型参数,并记下任何类型参数名称更改以及当前类提供的新类型参数的新位置.例如.从Thing
到NumberThing
时,T
变为N extends Number
.当类型实参是一个实际的类时,循环迭代会停止,例如Integer
,或者如果我们已经到达原始类,则在这种情况下,我们将报告类型参数名称和任何范围,例如N extends Number
.
It must create a Stack
of Class
es, 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
.
我还包括了两个附加类Superclass
和Subclass
,其中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屋!