I have been trying to determine the type of a field in a class. I\'ve seen all the introspection methods but haven\'t quite figured out how to do it. This is going to be use
The method field.getGenericType() returns a reference to the Type interface.
The real type could be an instance of TypeVariable or GenericArrayType or ParameterizedType or Class or something else I don't know about at this moment.
Different approaches are needed to retrieve the actual type of field.
Here is my solution for getting information about public fields in the form of a tree of TypeFieldTreeNode objects.
public class TypeFieldTreeNode {
public String fieldName;
public String typeSimpleName;
public String typeCanonicalName;
public String typeGenericName;
public List 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<>();
}
}
Main method:
private List getTypeFields(Class> clazz, Type genericType,
Map, Type> actualClassArguments) throws Exception {
if(clazz == null) {
return Collections.emptyList();
}
List fields = Arrays.stream(clazz.getDeclaredFields())
.filter(f -> Modifier.isPublic(f.getModifiers()) && !Modifier.isFinal(f.getModifiers()))
.collect(Collectors.toList());
List result = new ArrayList<>();
Map, Type> classArgumentsMap = mapTypeActualClassArguments(
clazz, genericType, actualClassArguments);
for(Field field : fields) {
result.add(getClassFieldData(field, classArgumentsMap));
}
if(clazz.getSuperclass() != null) {
List superClassFields =
getTypeFields(clazz.getSuperclass(), clazz.getGenericSuperclass(), classArgumentsMap);
result.addAll(superClassFields);
}
return result;
}
Next is listing of a core method that binds metadata of type TypeVariable of generic parameters with actual types of generic parameters. Method uses the mapping obtained earlier to restore the actual type of the generic parameter when that type is an instance of TypeVariable:
private Map, Type> mapTypeActualClassArguments(Class> clazz, Type genericType,
Map, Type> actualClassArguments) throws Exception {
if(!(genericType instanceof ParameterizedType)) {
return Collections.emptyMap();
}
Map, 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;
}
Get data about a field and all available fields of the class that is the type of this field:
private TypeFieldTreeNode getClassFieldData(Field field,
Map, 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, 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> where:
// public class MyClass2 { public T myField2; }
// public class MyClass { 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 where:
// public class MyClass { public T myField; }
else {
actualFieldClass = (Class>) actualFieldType;
result = new TypeFieldTreeNode(field.getName(), actualFieldClass.getSimpleName(),
actualFieldClass.getCanonicalName(), "");
}
List 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 where:
// public class MyClass { 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, Type> fieldTypeActualClassArguments = new HashMap<>();
// for example, field "myField2" of class MyClass2 where:
// public class MyClass2 { public MyClass myField2; }
// public class MyClass { 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 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, Type> actualClassArguments) throws Exception {
List 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, 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) + ">";
}
Usage:
public List getReturnTypeFields(Method method) throws Exception {
return getTypeFields(method.getReturnType(),
method.getGenericReturnType(), Collections.emptyMap());
}
The solution works as expected for the following test types:
MyClass2, MyClass, Double> MyClass3, MyClass> Where:
public class MyClass {
public T value;
public List list;
}
public class MyClass2 {
public T value;
public List strList;
public List genericList;
public int[] intArray;
public E[] genericArray;
public MyClass genericClass;
}
public class MyClass3 extends MyClass2 {
public T value3;
public List genericList3;
}