How can I determine the type of a generic field in Java?
Have a look at Obtaining Field Types from the Java Tutorial Trail: The Reflection API.
Basically, what you need to do is to get all java.lang.reflect.Field
of your class and call (check edit below). To get all object fields including public, protected, package and private access fields, simply use Field#getType()
on each of themClass.getDeclaredFields()
. Something like this:
for (Field field : Person.class.getDeclaredFields()) {
System.out.format("Type: %s%n", field.getType());
System.out.format("GenericType: %s%n", field.getGenericType());
}
EDIT: As pointed out by wowest in a comment, you actually need to call Field#getGenericType()
, check if the returned Type
is a ParameterizedType
and then grab the parameters accordingly. Use ParameterizedType#getRawType()
and ParameterizedType#getActualTypeArgument()
to get the raw type and an array of the types argument of a ParameterizedType
respectively. The following code demonstrates this:
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());
}
}
And would output:
Field: name - Type: class java.lang.String
Field: children - Raw type: interface java.util.List - Type args: class foo.Person
Here's an example that answers my question
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());
}
}
This outputs
field name: name -field type: class java.lang.String field name: children -raw type:interface java.util.List -type arg: class com.blah.Person
I haven't found any framework who determines a generic field type through the inheritance layers so i've written some method:
This logic determines the type through the field information and the current object class.
Listing 1 - logic:
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;
}
Listing 2 - Samples / Tests:
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);
}
I'm looking forward to hear your feedback!
take this snippet:
for (Field field : Person.class.getFields()) {
System.out.println(field.getType());
}
the key class is Field
Here's my take. It cannot handle every possible case (and surely has some bugs), but it does handle every case that occurs in my code so far. That includes these declarations, which should be a good start for many use cases:
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;
Implementation:
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();
}
}