如何确定 Java 中泛型字段的类型?
Posted
技术标签:
【中文标题】如何确定 Java 中泛型字段的类型?【英文标题】:How can I determine the type of a generic field in Java? 【发布时间】:2009-12-08 17:00:07 【问题描述】:我一直在尝试确定类中字段的类型。我已经看过所有的内省方法,但还没有完全弄清楚如何去做。这将用于从 java 类生成 xml/json。我在这里查看了一些问题,但没有找到我真正需要的。
例子:
class Person
public final String name;
public final List<Person> children;
当我编组这个对象时,我需要知道chidren
字段是Person
类型的对象列表,所以我可以正确编组它。
我试过了
for (Field field : Person.class.getDeclaredFields())
System.out.format("Type: %s%n", field.getType());
但这只会告诉我这是List
,而不是Person
s 中的List
谢谢
【问题讨论】:
列表查看 Java 教程 Trail: The Reflection API 中的 Obtaining Field Types。
基本上,您需要做的是获取您班级的所有java.lang.reflect.Field
并在每个人上调用(检查下面的编辑)。要获取所有对象字段,包括公共、受保护、包和私有访问字段,只需使用Field#getType()
Class.getDeclaredFields()
。像这样的:
for (Field field : Person.class.getDeclaredFields())
System.out.format("Type: %s%n", field.getType());
System.out.format("GenericType: %s%n", field.getGenericType());
编辑:正如wowest在评论中指出的那样,您实际上需要调用Field#getGenericType()
,检查返回的Type
是否是ParameterizedType
,然后相应地获取参数.使用ParameterizedType#getRawType()
和ParameterizedType#getActualTypeArgument()
分别获取ParameterizedType
的类型参数的原始类型和数组。下面的代码演示了这一点:
for (Field field : Person.class.getDeclaredFields())
System.out.print("Field: " + field.getName() + " - ");
Type type = field.getGenericType();
if (type instanceof ParameterizedType)
ParameterizedType pType = (ParameterizedType)type;
System.out.print("Raw type: " + pType.getRawType() + " - ");
System.out.println("Type args: " + pType.getActualTypeArguments()[0]);
else
System.out.println("Type: " + field.getType());
并且会输出:
Field: name - Type: class java.lang.String
Field: children - Raw type: interface java.util.List - Type args: class foo.Person
【讨论】:
帕斯卡——你非常接近。你想要 Type type = Field.getGenericType();然后检查它是否是ParameterizedType,然后获取参数。这最终变成了一个兔子洞——提问者需要定义限制或准备编写一些漂亮的代码。 感谢 wowest,这正是我需要与 ParameterizedType.getRawType 和 ParameterizedType.getActualArguments 结合使用的。 我不知道如何将您的评论标记为正确答案,所以我只回答我自己的问题,以便发布示例 @Juan Mendes,您应该会在每个答案的左侧看到一个勾号图标,在投票编号旁边,只需单击属于正确答案的那个,它就会变成绿色。 我知道我可以将此答案标记为正确答案。然而,直到 Pascal 编辑它以同意 wowest 的评论之前,它是不正确的。无论如何,我在下面列出的示例包含了我需要的所有信息,但在这么多人帮助我之后,我不想将自己的答案标记为正确答案。 Pascal 的示例仍然缺少关键方法(ParameterizedType.getRawType 和 ParameterizedType.getActualArguments?)。 Pascal,您能否编辑示例以显示正在使用的这两种方法?【参考方案2】:这是一个回答我问题的例子
class Person
public final String name;
public final List<Person> children;
//in main
Field[] fields = Person.class.getDeclaredFields();
for (Field field : fields)
Type type = field.getGenericType();
System.out.println("field name: " + field.getName());
if (type instanceof ParameterizedType)
ParameterizedType ptype = (ParameterizedType) type;
ptype.getRawType();
System.out.println("-raw type:" + ptype.getRawType());
System.out.println("-type arg: " + ptype.getActualTypeArguments()[0]);
else
System.out.println("-field type: " + field.getType());
这个输出
字段名称:名称 -字段类型:类 java.lang.String 字段名称:儿童 -原始类型:接口 java.util.List -type arg:com.blah.Person 类【讨论】:
【参考方案3】:我还没有找到任何通过继承层确定通用字段类型的框架,所以我写了一些方法:
这个逻辑通过字段信息和当前对象类来判断类型。
清单 1 - 逻辑:
public static Class<?> determineType(Field field, Object object)
Class<?> type = object.getClass();
return (Class<?>) getType(type, field).type;
protected static class TypeInfo
Type type;
Type name;
public TypeInfo(Type type, Type name)
this.type = type;
this.name = name;
private static TypeInfo getType(Class<?> clazz, Field field)
TypeInfo type = new TypeInfo(null, null);
if (field.getGenericType() instanceof TypeVariable<?>)
TypeVariable<?> genericTyp = (TypeVariable<?>) field.getGenericType();
Class<?> superClazz = clazz.getSuperclass();
if (clazz.getGenericSuperclass() instanceof ParameterizedType)
ParameterizedType paramType = (ParameterizedType) clazz.getGenericSuperclass();
TypeVariable<?>[] superTypeParameters = superClazz.getTypeParameters();
if (!Object.class.equals(paramType))
if (field.getDeclaringClass().equals(superClazz))
// this is the root class an starting point for this search
type.name = genericTyp;
type.type = null;
else
type = getType(superClazz, field);
if (type.type == null || type.type instanceof TypeVariable<?>)
// lookup if type is not found or type needs a lookup in current concrete class
for (int j = 0; j < superClazz.getTypeParameters().length; ++j)
TypeVariable<?> superTypeParam = superTypeParameters[j];
if (type.name.equals(superTypeParam))
type.type = paramType.getActualTypeArguments()[j];
Type[] typeParameters = clazz.getTypeParameters();
if (typeParameters.length > 0)
for (Type typeParam : typeParameters)
TypeVariable<?> objectOfComparison = superTypeParam;
if(type.type instanceof TypeVariable<?>)
objectOfComparison = (TypeVariable<?>)type.type;
if (objectOfComparison.getName().equals(((TypeVariable<?>) typeParam).getName()))
type.name = typeParam;
break;
break;
else
type.type = field.getGenericType();
return type;
清单 2 - 样本/测试:
class GenericSuperClass<E, T, A>
T t;
E e;
A a;
BigDecimal b;
class GenericDefinition extends GenericSuperClass<Integer, Integer, Integer>
@Test
public void testSimpleInheritanceTypeDetermination()
GenericDefinition gd = new GenericDefinition();
Field field = ReflectionUtils.getField(gd, "t");
Class<?> clazz = ReflectionUtils.determineType(field, gd);
Assert.assertEquals(clazz, Integer.class);
field = ReflectionUtils.getField(gd, "b");
clazz = ReflectionUtils.determineType(field, gd);
Assert.assertEquals(clazz, BigDecimal.class);
class MiddleClass<A, E> extends GenericSuperClass<E, Integer, A>
// T = Integer, E = String, A = Double
class SimpleTopClass extends MiddleClass<Double, String>
@Test
public void testSimple2StageInheritanceTypeDetermination()
SimpleTopClass stc = new SimpleTopClass();
Field field = ReflectionUtils.getField(stc, "t");
Class<?> clazz = ReflectionUtils.determineType(field, stc);
Assert.assertEquals(clazz, Integer.class);
field = ReflectionUtils.getField(stc, "e");
clazz = ReflectionUtils.determineType(field, stc);
Assert.assertEquals(clazz, String.class);
field = ReflectionUtils.getField(stc, "a");
clazz = ReflectionUtils.determineType(field, stc);
Assert.assertEquals(clazz, Double.class);
class TopMiddleClass<A> extends MiddleClass<A, Double>
// T = Integer, E = Double, A = Float
class ComplexTopClass extends TopMiddleClass<Float>
@Test void testComplexInheritanceTypDetermination()
ComplexTopClass ctc = new ComplexTopClass();
Field field = ReflectionUtils.getField(ctc, "t");
Class<?> clazz = ReflectionUtils.determineType(field, ctc);
Assert.assertEquals(clazz, Integer.class);
field = ReflectionUtils.getField(ctc, "e");
clazz = ReflectionUtils.determineType(field, ctc);
Assert.assertEquals(clazz, Double.class);
field = ReflectionUtils.getField(ctc, "a");
clazz = ReflectionUtils.determineType(field, ctc);
Assert.assertEquals(clazz, Float.class);
class ConfusingClass<A, E> extends MiddleClass<E, A>
// T = Integer, E = Double, A = Float ; this class should map between a and e
class TopConfusingClass extends ConfusingClass<Double, Float>
@Test
public void testConfusingNamingConvetionWithInheritance()
TopConfusingClass tcc = new TopConfusingClass();
Field field = ReflectionUtils.getField(tcc, "t");
Class<?> clazz = ReflectionUtils.determineType(field, tcc);
Assert.assertEquals(clazz, Integer.class);
field = ReflectionUtils.getField(tcc, "e");
clazz = ReflectionUtils.determineType(field, tcc);
Assert.assertEquals(clazz, Double.class);
field = ReflectionUtils.getField(tcc, "a");
clazz = ReflectionUtils.determineType(field, tcc);
Assert.assertEquals(clazz, Float.class);
field = ReflectionUtils.getField(tcc, "b");
clazz = ReflectionUtils.determineType(field, tcc);
Assert.assertEquals(clazz, BigDecimal.class);
class Pojo
Byte z;
@Test
public void testPojoDetermineType()
Pojo pojo = new Pojo();
Field field = ReflectionUtils.getField(pojo, "z");
Class<?> clazz = ReflectionUtils.determineType(field, pojo);
Assert.assertEquals(clazz, Byte.class);
我期待听到您的反馈!
【讨论】:
谢谢!这种方法帮助很大。我建议添加检查 type.type 是否是ParameterizedTypeImpl
的实例,在这种情况下.getRawType()
方法会产生该类型。当类型为List<Double>
时,这将获取List
。 (否则可能会得到ParameterizedTypeImpl cannot be cast to java.lang.Class
)【参考方案4】:
拿着这个sn-p:
for (Field field : Person.class.getFields())
System.out.println(field.getType());
关键类是Field
【讨论】:
只有Class.getFields
的公共字段(包括静态)。也将是擦除类型。
那我误解了问题【参考方案5】:
这是我的看法。它不能处理所有可能的情况(并且肯定有一些错误),但它确实处理了到目前为止我的代码中出现的所有情况。这包括这些声明,这对于许多用例来说应该是一个好的开始:
private int primitiveField1;
private Object field1;
private List<Integer> field2;
private Map<Integer, String> field3;
private Map<? extends String, List<Map<Class<?>, Object>>> field4;
private char[] array1;
private Character[] array2;
private Class<? extends Integer>[] array3;
private List<Integer>[] array4;
private InnerClass<String> innerClass;
实施:
public static String getDeclaration(Field field)
return getDeclaration(field.getGenericType());
private static String getDeclaration(Type genericType)
if(genericType instanceof ParameterizedType)
// types with parameters
ParameterizedType parameterizedType = (ParameterizedType) genericType;
String declaration = parameterizedType.getRawType().getTypeName();
declaration += "<";
Type[] typeArgs = parameterizedType.getActualTypeArguments();
for(int i = 0; i < typeArgs.length; i++)
Type typeArg = typeArgs[i];
if(i > 0)
declaration += ", ";
// note: recursive call
declaration += getDeclaration(typeArg);
declaration += ">";
declaration = declaration.replace('$', '.');
return declaration;
else if(genericType instanceof Class<?>)
Class<?> clazz = (Class<?>) genericType;
if(clazz.isArray())
// arrays
return clazz.getComponentType().getCanonicalName() + "[]";
else
// primitive and types without parameters (normal/standard types)
return clazz.getCanonicalName();
else
// e.g. WildcardTypeImpl (Class<? extends Integer>)
return genericType.getTypeName();
【讨论】:
你能评论一下你能做什么而 javaBeCool 不能吗?我想知道两者的混合是否是处理递归泛型和继承所需要的。见***.com/a/19363555/227299 @JuanMendes javaBeCool 似乎解决了一个不同的用例。我的答案是将字段声明作为字符串获取,我在代码生成项目中使用它。我原来的问题被标记为这个问题的重复 - 这就是我在此处发布的原因 (***.com/q/45083186/1124509)。【参考方案6】:正如 dfa 指出的那样,您可以使用 java.lang.reflect.Field.getType
获取已擦除类型。您可以使用Field.getGenericType
获得泛型类型(可能有通配符和绑定的泛型参数以及各种疯狂)。您可以通过Class.getDeclaredFields
获取字段(Class.getFields
将为您提供公共字段(包括 supertpye 的字段)- 毫无意义)。要获取基本类型字段,请通过Class.getSuperclass
。请注意检查来自 Field.getModifiers
的修饰符 - 静态字段可能不会对您感兴趣。
【讨论】:
【参考方案7】:field.getGenericType()
方法返回对Type
接口的引用。
真正的类型可能是 TypeVariable
或 GenericArrayType
或 ParameterizedType
或 Class
或其他我目前不知道的实例。
需要不同的方法来检索字段的实际类型。
这是我以 TypeFieldTreeNode 对象树的形式获取公共字段信息的解决方案。
public class TypeFieldTreeNode
public String fieldName;
public String typeSimpleName;
public String typeCanonicalName;
public String typeGenericName;
public List<TypeFieldTreeNode> children;
public TypeFieldTreeNode(String fieldName, String typeSimpleName, String typeCanonicalName, String genericTypeName)
this.fieldName = fieldName;
this.typeSimpleName = typeSimpleName;
this.typeCanonicalName = typeCanonicalName;
this.typeGenericName = genericTypeName;
this.children = new ArrayList<>();
主要方法:
private List<TypeFieldTreeNode> getTypeFields(Class<?> clazz, Type genericType,
Map<TypeVariable<?>, Type> actualClassArguments) throws Exception
if(clazz == null)
return Collections.emptyList();
List<Field> fields = Arrays.stream(clazz.getDeclaredFields())
.filter(f -> Modifier.isPublic(f.getModifiers()) && !Modifier.isFinal(f.getModifiers()))
.collect(Collectors.toList());
List<TypeFieldTreeNode> result = new ArrayList<>();
Map<TypeVariable<?>, Type> classArgumentsMap = mapTypeActualClassArguments(
clazz, genericType, actualClassArguments);
for(Field field : fields)
result.add(getClassFieldData(field, classArgumentsMap));
if(clazz.getSuperclass() != null)
List<TypeFieldTreeNode> superClassFields =
getTypeFields(clazz.getSuperclass(), clazz.getGenericSuperclass(), classArgumentsMap);
result.addAll(superClassFields);
return result;
接下来是一个核心方法的列表,它将泛型参数的 TypeVariable 类型的元数据与泛型参数的实际类型绑定。 当泛型参数的类型是 TypeVariable 的实例时,方法使用前面获得的映射来恢复该泛型参数的实际类型:
private Map<TypeVariable<?>, Type> mapTypeActualClassArguments(Class<?> clazz, Type genericType,
Map<TypeVariable<?>, Type> actualClassArguments) throws Exception
if(!(genericType instanceof ParameterizedType))
return Collections.emptyMap();
Map<TypeVariable<?>, Type> result = new HashMap<>();
Type[] actualTypeParametersTypes = ((ParameterizedType) genericType).getActualTypeArguments();
TypeVariable<?>[] classTypeParameters = clazz.getTypeParameters();
for (int i = 0; i < classTypeParameters.length; i++)
if(actualTypeParametersTypes[i] instanceof TypeVariable<?>)
TypeVariable<?> fieldTypeVariable = (TypeVariable<?>) actualTypeParametersTypes[i];
if(actualClassArguments.containsKey(fieldTypeVariable))
actualTypeParametersTypes[i] = actualClassArguments.get(fieldTypeVariable);
else
throw new Exception(String.format("For generic parameter %s of type %s, the corresponding actual type of generic parameter was not found",
classTypeParameters[i].getName(), genericType.getTypeName()));
result.put(classTypeParameters[i], actualTypeParametersTypes[i]);
return result;
获取有关字段的数据以及属于该字段类型的类的所有可用字段:
private TypeFieldTreeNode getClassFieldData(Field field,
Map<TypeVariable<?>, Type> actualClassArguments) throws Exception
Class<?> fieldClass = field.getType();
Type fieldGenericType = field.getGenericType();
TypeFieldTreeNode result = null;
// if type of the field is a generic parameter of the class containing the field
if(fieldGenericType instanceof TypeVariable<?>)
Type actualFieldType = null;
Class<?> actualFieldClass = null;
Map<TypeVariable<?>, Type> fieldTypeActualClassArguments = new HashMap<>();
TypeVariable<?> fieldTypeVariable = (TypeVariable<?>) fieldGenericType;
if(actualClassArguments.containsKey(fieldTypeVariable))
actualFieldType = actualClassArguments.get(fieldTypeVariable);
else
throw new Exception(String.format("For a field %s of type %s from class %s, the corresponding actual type of generic parameter was not found",
field.getName(), fieldGenericType.getTypeName(), field.getDeclaringClass().getCanonicalName()));
// for example, field "myField2" of class MyClass2<MyClass<Integer>> where:
// public class MyClass2<T> public T myField2;
// public class MyClass<T> public T myField;
if(actualFieldType instanceof ParameterizedType)
actualFieldClass = (Class<?>)((ParameterizedType) actualFieldType).getRawType();
result = new TypeFieldTreeNode(field.getName(), actualFieldClass.getSimpleName(),
actualFieldClass.getCanonicalName(), actualFieldType.getTypeName());
fieldTypeActualClassArguments = mapTypeActualClassArguments(actualFieldClass, actualFieldType, actualClassArguments);
// for example, field "myField" of class MyClass<Integer> where:
// public class MyClass<T> public T myField;
else
actualFieldClass = (Class<?>) actualFieldType;
result = new TypeFieldTreeNode(field.getName(), actualFieldClass.getSimpleName(),
actualFieldClass.getCanonicalName(), "");
List<Field> childFields = Arrays.stream(actualFieldClass.getFields())
.filter(f -> !Modifier.isFinal(f.getModifiers()))
.collect(Collectors.toList());
for (Field childField : childFields)
result.children.add(getClassFieldData(childField, fieldTypeActualClassArguments));
// if the field is an array and the type of the elements of the array is a generic parameter of the class containing the field
// for example, field "myField" of class MyClass<Integer> where:
// public class MyClass<T> public T[] myField;
else if(fieldGenericType instanceof GenericArrayType)
Type genericComponentType = ((GenericArrayType) fieldGenericType).getGenericComponentType();
if(genericComponentType instanceof TypeVariable<?>)
if(actualClassArguments.containsKey(genericComponentType))
Type actualArrayComponentType = actualClassArguments.get(genericComponentType);
assert !(actualArrayComponentType instanceof ParameterizedType);
Class<?> actualArrayClass = (Class<?>) actualArrayComponentType;
result = new TypeFieldTreeNode(field.getName(), actualArrayClass.getSimpleName() + "[]",
actualArrayClass.getCanonicalName() + "[]", "");
else
throw new Exception(String.format("For a field %s of type %s from class %s, the corresponding actual type of generic parameter was not found",
field.getName(), fieldGenericType.getTypeName(), field.getDeclaringClass().getCanonicalName()));
else
throw new Exception(String.format("Unknown array genericComponentType: %s", genericComponentType.getClass().getCanonicalName()));
else
result = new TypeFieldTreeNode(field.getName(), fieldClass.getSimpleName(), fieldClass.getCanonicalName(), "");
Map<TypeVariable<?>, Type> fieldTypeActualClassArguments = new HashMap<>();
// for example, field "myField2" of class MyClass2<Integer> where:
// public class MyClass2<T> public MyClass<T> myField2;
// public class MyClass<T> public T myField;
if(fieldGenericType instanceof ParameterizedType)
// custom generic type name creator for situations when actual type arguments can be of type TypeVariable
result.typeGenericName = getGenericTypeName((ParameterizedType)fieldGenericType, actualClassArguments);
fieldTypeActualClassArguments = mapTypeActualClassArguments(fieldClass, fieldGenericType, actualClassArguments);
List<Field> childFields = Arrays.stream(fieldClass.getFields()).filter(f -> !Modifier.isFinal(f.getModifiers()))
.collect(Collectors.toList());
for (Field childField : childFields)
result.children.add(getClassFieldData(childField, fieldTypeActualClassArguments));
return result;
private String getGenericTypeName(ParameterizedType parameterizedType,
Map<TypeVariable<?>, Type> actualClassArguments) throws Exception
List<String> genericParamJavaTypes = new ArrayList<>();
for(Type typeArgument : parameterizedType.getActualTypeArguments())
if (typeArgument instanceof TypeVariable<?>)
TypeVariable<?> typeVariable = (TypeVariable<?>) typeArgument;
if(actualClassArguments.containsKey(typeVariable))
typeArgument = actualClassArguments.get(typeVariable);
else
throw new Exception(String.format("For generic parameter %s of type %s, the corresponding actual type of generic parameter was not found",
typeArgument.getTypeName(), parameterizedType.getTypeName()));
if(typeArgument instanceof ParameterizedType)
ParameterizedType parameterizedTypeArgument = (ParameterizedType) typeArgument;
Map<TypeVariable<?>, Type> typeActualClassArguments = mapTypeActualClassArguments(
(Class<?>)parameterizedTypeArgument.getRawType(),
typeArgument, actualClassArguments);
genericParamJavaTypes.add(getGenericTypeName((ParameterizedType) typeArgument, typeActualClassArguments));
else if (typeArgument instanceof Class<?>)
genericParamJavaTypes.add(((Class<?>) typeArgument).getCanonicalName());
else
throw new Exception(String.format("For generic parameter %s of type %s, the corresponding actual type of generic parameter was not found", typeArgument.getTypeName()));
Class<?> rawType = (Class<?>) parameterizedType.getRawType();
return rawType.getCanonicalName() + "<" + String.join(", ", genericParamJavaTypes) + ">";
用法:
public List<TypeFieldTreeNode> getReturnTypeFields(Method method) throws Exception
return getTypeFields(method.getReturnType(),
method.getGenericReturnType(), Collections.emptyMap());
对于以下测试类型,该解决方案按预期工作:
MyClass2<MyClass<Integer>, MyClass<Boolean>, Double>
MyClass3<MyClass<Integer>, MyClass<Double>>
地点:
public class MyClass<T>
public T value;
public List<String> list;
public class MyClass2<T, V, E>
public T value;
public List<String> strList;
public List<V> genericList;
public int[] intArray;
public E[] genericArray;
public MyClass<E> genericClass;
public class MyClass3<T, V> extends MyClass2<T, V, Boolean>
public T value3;
public List<V> genericList3;
【讨论】:
【参考方案8】:public static Type[] getGenericTypes(Field field)
ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
return actualTypeArguments;
class User
...
private Set<Authority> authorities = new HashSet<>();
...
/// usage
Class c = User.class;
Field field = c.getDeclaredField("authorities");
Type[] types = getGenericTypes(field);
log.info("Types: ", types);
/// result
Types: class com.fajar.medicalinventory.entity.Authority
【讨论】:
以上是关于如何确定 Java 中泛型字段的类型?的主要内容,如果未能解决你的问题,请参考以下文章