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

前端 未结 7 914
无人共我
无人共我 2020-11-27 13:25

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

7条回答
  •  日久生厌
    2020-11-27 13:58

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

提交回复
热议问题