How can I determine the type of a generic field in Java?

56,681

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 Field#getType() on each of them (check edit below). To get all object fields including public, protected, package and private access fields, simply use Class.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();
    }
  }
Share:
56,681
Ruan Mendes
Author by

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, 2021

Comments

  • Ruan Mendes
    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 type Person, 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 a List of Persons

    Thanks

  • Brett
    Brett over 14 years
    That's public fields only (including static) with Class.getFields. Also it'll be the erased type.
  • dfa
    dfa over 14 years
    then I misunderstood the question
  • wowest
    wowest over 14 years
    Pascal -- 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
    Ruan Mendes over 14 years
    Thanks wowest, that is exactly what I need in conjunction with ParameterizedType.getRawType and ParameterizedType.getActualArguments.
  • Ruan Mendes
    Ruan Mendes over 14 years
    I 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
    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
    Ruan Mendes over 14 years
    I 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
    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
    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
    Jeff G almost 8 years
    Is there any case where the return value of getGenericType() and getType() are different, when the result of getGenericType() is NOT ParameterizedType? My cursory testing indicates that they are the same in that case.
  • Ruan Mendes
    Ruan Mendes almost 7 years
    Can 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
    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
    Václav Blažej over 6 years
    Thanks! 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 fetch List when type is List<Double>. (otherwise it might get ParameterizedTypeImpl cannot be cast to java.lang.Class)