简体   繁体   中英

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

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 Person s

Thanks

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

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();
    }
  }

As dfa points out, you can get the erased type with java.lang.reflect.Field.getType . You can get the generic type with Field.getGenericType (which may have wildcards and bound generic parameters and all sorts of craziness). You can get the fields through Class.getDeclaredFields ( Class.getFields will give you public fields (including those of the supertpye) - pointless). To get the base type fields, go through Class.getSuperclass . Note to check modifiers from Field.getModifiers - static fields probably will not be interesting to you.

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<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<>();
    }
}

Main method:

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;
}

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<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;
    }

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<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) + ">";
}

Usage:

public List<TypeFieldTreeNode> 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<Integer>, MyClass<Boolean>, Double>
  • MyClass3<MyClass<Integer>, MyClass<Double>>

Where:

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;
}
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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM