How can I determine the type of a generic field in Java?
Solution 1
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
Solution 2
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
Solution 3
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!
Solution 4
take this snippet:
for (Field field : Person.class.getFields()) {
System.out.println(field.getType());
}
the key class is Field
Solution 5
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();
}
}
Ruan Mendes
Client side/ middle tier web developer. Have programmed in C, C++, C#, Groovy, Java, ActionScript, Lingo, JavaScript, PHP, TypeScript. Basic My language of choice is TypeScript, on the browser or Deno. Technologies I've worked with substantially: HTML, CSS, DOM, AJAX, Angular, React, jQuery, Google Closure Templates, Sencha touch, Ext-JS ASP, PHP, JSP, Struts, Velocity, Node.js, Kohana Windows, Unix, OpenVMS, Solaris Ant, make, maven XML-RPC, RESTful services JSUnit, JUnit, PhpUnit, Karma, Jasmine, js-test-driver, NUnit, YUI tests Selenium, Cucumber, Cypress Grails ASP.NET
Updated on April 21, 2021Comments
-
Ruan Mendes about 3 years
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 used to generate xml/json from a java class. I've looked at a number of the questions here but haven't found exactly what I need.
Example:
class Person { public final String name; public final List<Person> children; }
When I marshall this object, I need to know that the
chidren
field is a list of objects of typePerson
, so I can marshall it properly.I had tried
for (Field field : Person.class.getDeclaredFields()) { System.out.format("Type: %s%n", field.getType()); }
But this will only tell me that it's a
List
, not aList
ofPerson
sThanks
-
Brett over 14 yearsThat's public fields only (including static) with
Class.getFields
. Also it'll be the erased type. -
dfa over 14 yearsthen I misunderstood the question
-
wowest over 14 yearsPascal -- you're super close. you want Type type = Field.getGenericType(); And then check if it's a ParameterizedType, then grab the parameters. This ends up being a rabbit hole -- asker needs to define limits or be prepared to write some pretty fancy code.
-
Ruan Mendes over 14 yearsThanks wowest, that is exactly what I need in conjunction with ParameterizedType.getRawType and ParameterizedType.getActualArguments.
-
Ruan Mendes over 14 yearsI can't figure out how to mark your comment as the right answer so I'll just answer my own question so I can post an example
-
Iker Jimenez over 14 years@Juan Mendes, you should be seeing a tick icon to the left of each of the answers, next to the vote number, just click on the one that belongs to the correct answer so it turns green.
-
Ruan Mendes over 14 yearsI knew that I could mark this answer as the right answer. However, it was not correct until Pascal edited it to agree with wowest's comment. In any case, the example I listed below contains all the info I need but I don't want to mark my own answer as the right answer after so many people helped me. Pascal's example is still missing critical methods (ParameterizedType.getRawType and ParameterizedType.getActualArguments?). Pascal, could you edit the example so it shows those two methods being used?
-
Devanshu Mevada over 14 years@Juan I initially didn't wanted to provide all the code as you actually did the job and answered your own question entirely. But it was so kindly asked that I updated my answer (with something very similar to what you wrote, of course).
-
Ruan Mendes over 14 years@Pascal, This is the first time I actually posted a question here at stack overflow and found that people here are courteous and smart when asking and responding to questions. Other forums I've used are usually only one or the other :). Stack overflow will surely become my first place to ask questions and I will definitely try to answer questions as I can.
-
Jeff G almost 8 yearsIs there any case where the return value of
getGenericType()
andgetType()
are different, when the result ofgetGenericType()
is NOTParameterizedType
? My cursory testing indicates that they are the same in that case. -
Ruan Mendes almost 7 yearsCan you comment on what yours can do that javaBeCool's can't? I wonder if a mix of both is what's needed to handle recursive generics plus inheritance. See stackoverflow.com/a/19363555/227299
-
Reto Höhener almost 7 years@JuanMendes javaBeCool seems to address a different use case. My answer is about getting the field declaration as a string, I use it in a code generation project. My original question was marked as duplicate of this question - that's why I posted here (stackoverflow.com/q/45083186/1124509).
-
Václav Blažej over 6 yearsThanks! This method helped a lot. I would recommend adding check whether type.type is an instance of
ParameterizedTypeImpl
, in which case the.getRawType()
method yields the type. This will fetchList
when type isList<Double>
. (otherwise it might getParameterizedTypeImpl cannot be cast to java.lang.Class
)