Generic parsing of JAVA reflection

Posted by DjMikeS on Mon, 29 Nov 2021 00:53:57 +0100

Generally, we use Class to describe data types. In fact, there is a more general type interface type in JDK. Type is the common parent interface of all types in JDK, and Class is also one of the implementation classes of type.

public interface Type {
    default String getTypeName() {
        return this.toString();
    }
}

Before there were no generics, Java had only raw type s. At this time, all types were described by Class.

public final class Class<T> 
        implements Serializable, GenericDeclaration, Type, AnnotatedElement

After adding generics, the JDK extends types and adds the following four types outside Class.

typedescribe
ParameterizedTypeParameterized types, that is, generic types, such as list < string >, map < integer, string >
GenericArrayTypeGeneric array type, for example: T []
TypeVariableType variable type, for example: T in list < T >
WildcardTypeWildcard types are not JAVA types, but generic expressions, such as:? super T,? extends T

Usage of ParameterizedType

ParameterizedType represents a parameterized type. The so-called parameter refers to the generic type in < >.

public interface ParameterizedType extends Type {
    // Get generic types within < >
    Type[] getActualTypeArguments();
    // Gets the original type, and returns O if the generic structure is O < T >
    Type getRawType();
    // If the generic structure is o < T >. I < s >, return the o < T > of the outer layer
    Type getOwnerType();
    
}

Gets the generic information of the parent class

There is a getGenericSuperclass() method in the Class class, which is used to obtain the parent Class with generic information. If the parent Class does not have generic information, it is equivalent to the getSuperclass() method.

Create a parent class without generics and implement

public class GenericService {
}

public class UserService extends GenericService {
}
public class GenericTest {

    public static void main(String[] args) {
        Type genericSuperClass = UserService.class.getGenericSuperclass();
        Class<? super UserService> superclass = UserService.class.getSuperclass();
        System.out.println(genericSuperClass);
        System.out.println(superclass);
        System.out.println(genericSuperClass == superclass);
    }

}

The output is as follows

class test.GenericService
class test.GenericService
true

You can see that both getGenericSuperclass and getSuperclass return the same Class.

Next, use generics to verify again. Here you need two classes UserRepository and User, which can be created by yourself.

public class GenericService<T, M> {
}

public class UserService extends GenericService<UserRepository, User> {
}

Running again, you can see that the objects returned by the two methods are different. The getGenericSuperclass method returns the ParameterizedType with generic information.

test.GenericService<test.UserRepository, test.User>
class test.GenericService
false

Generics can be further obtained through ParameterizedType

public class GenericTest {

    public static void main(String[] args) {
        Type genericSuperClass = UserService.class.getGenericSuperclass();
        ParameterizedType parameterizedType = (ParameterizedType) genericSuperClass;
        // Get original type Class
        displayType(parameterizedType.getRawType());
        // Get generics
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        for (Type argument : actualTypeArguments) {
            displayType(argument);
        }
    }

    public static void displayType(Type type) {
        System.out.println(type.getTypeName() + " --- " + type.getClass().getSimpleName());
    }

}

Output results

test.GenericService --- Class
test.UserRepository --- Class
test.User --- Class

Gets the generic information of the interface

A getGenericInterfaces() method is also provided in the Class class to obtain the interface with generic information.

Create two interfaces, one without generics and one with generics

public interface IA {
}

public interface IB<T, P extends Serializable> {
}

Create an implementation class

public class Impl implements IA, IB<UserRepository, User> {
}

Gets the generic type of the interface

public class GenericTest {

    public static void main(String[] args) {
        Type[] genericInterfaces = Impl.class.getGenericInterfaces();
        for (Type type : genericInterfaces) {
            if (type instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) type;
                displayType(parameterizedType);
                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                for (Type argument : actualTypeArguments) {
                    displayType(argument);
                }
            } else {
                displayType(type);
            }
            System.out.println("-------------------------------");
        }
    }

    public static void displayType(Type type) {
        System.out.println(type.getTypeName() + " --- " + type.getClass().getSimpleName());
    }

}

Output results

test.IA --- Class
-------------------------------
test.IB<test.UserRepository, test.User> --- ParameterizedTypeImpl
test.UserRepository --- Class
test.User --- Class
-------------------------------

Provide a tool class to get the parent class and interface generics

public class ReflectUtils {

    /**
     * Gets the generic type at the specified location of the parent class
     */
    public static Class<?> getSuperClassGenericType(Class<?> clazz, int index) {
        Type genType = clazz.getGenericSuperclass();
        if (!(genType instanceof ParameterizedType)) {
            log.warn(String.format("Warn: %s's superclass not ParameterizedType", clazz.getSimpleName()));
            return Object.class;
        }
        return indexOfGenericType(clazz, (ParameterizedType) genType, index);
    }

    /**
     * Gets the generic type of the specified interface at the specified location
     */
    public static Class<?> getInterfaceGenericType(Class<?> clazz, Class<?> target, int index) {
        for (Type genericInterface : clazz.getGenericInterfaces()) {
            if (genericInterface instanceof ParameterizedType) {
                if (((ParameterizedType) genericInterface).getRawType() == target) {
                    return indexOfGenericType(clazz, (ParameterizedType) genericInterface, index);
                }
            } else if (genericInterface == target) {
                log.warn(String.format("Warn: %s's interface not ParameterizedType", clazz.getSimpleName()));
                return Object.class;
            }
        }
        return Object.class;
    }

    public static Class<?> indexOfGenericType(Class<?> clazz, ParameterizedType type, int index) {
        Type[] params = type.getActualTypeArguments();
        if (index >= params.length || index < 0) {
            log.warn(String.format("Warn: Index: %s, Size of %s's Parameterized Type: %s .", index,
                    clazz.getSimpleName(), params.length));
            return Object.class;
        }
        if (!(params[index] instanceof Class)) {
            log.warn(String.format("Warn: %s not set the actual class on superclass generic parameter",
                    clazz.getSimpleName()));
            return Object.class;
        }
        return (Class<?>) params[index];
    }
}

Gets the generic type of the field

A getGenericType() method is provided in the Field class to obtain the Field type with generic information. If there is no generic information, this method is equivalent to getType().

public class GenericTest {

    private List<String> list;
    private List unknownList;
    private Map<String, Long> map;
    private Map unknownMap;
    private Map.Entry<String, Long> entry;

    public static void main(String[] args) {
        Field[] fields = GenericTest.class.getDeclaredFields();
        for (Field f : fields) {
            System.out.println(f.getName() + " is ParameterizedType: " + (f.getGenericType() instanceof ParameterizedType));
        }
    }
}

The output is as follows. You can see whether there is a generic type, not whether there is a generic type in the Class declaration, but whether there is a generic type in the variable declaration. For example, if unknownList does not declare a generic type, the getGenericType() method returns a Class instead of ParameterizedType.

list is ParameterizedType: true
unknownList is ParameterizedType: false
map is ParameterizedType: true
unknownMap is ParameterizedType: false
entry is ParameterizedType: true

Get generic information for generic variables

public class GenericTest {

    private List<String> list;
    private List unknownList;
    private Map<String, Long> map;
    private Map unknownMap;
    private Map.Entry<String, Long> entry;

    public static void main(String[] args) {
        Field[] fields = GenericTest.class.getDeclaredFields();
        for (Field f : fields) {
            if (f.getGenericType() instanceof ParameterizedType) {
                System.out.print(f.getName() + "<");
                ParameterizedType genericType = (ParameterizedType) f.getGenericType();
                Type[] typeArguments = genericType.getActualTypeArguments();
                int i = 0;
                for (Type argument : typeArguments) {
                    if (i++ > 0) {
                        System.out.print(",");
                    }
                    System.out.print(argument.getTypeName());
                }
                System.out.println(">");
            }
        }
    }
}

The output is as follows

list<java.lang.String>
map<java.lang.String,java.lang.Long>
entry<java.lang.String,java.lang.Long>

Here you can also take a look at the usage of the getOwnerType() method

public class GenericTest {

    private List<String> list;
    private List unknownList;
    private Map<String, Long> map;
    private Map unknownMap;
    private Map.Entry<String, Long> entry;

    public static void main(String[] args) {
        Field[] fields = GenericTest.class.getDeclaredFields();
        for (Field f : fields) {
            if (f.getGenericType() instanceof ParameterizedType) {
                ParameterizedType genericType = (ParameterizedType) f.getGenericType();
                System.out.println(f.getName() + " ownerType is " +
                        (genericType.getOwnerType() == null ? "null" : genericType.getOwnerType().getTypeName()));
            }
        }
    }
}

The output is as follows. You can see that when the generic structure is o < T >. I < s > type, calling getOwnerType() will return o < T > of the outer layer, otherwise it will return null.

list ownerType is null
map ownerType is null
entry ownerType is java.util.Map

Gets generic information for the method

public class GenericTest {

    public static void main(String[] args) {
        Method[] methods = GenericTest.class.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().equals("test")) {
                Type[] genericParameterTypes = method.getGenericParameterTypes();
                for (Type genericParameterType : genericParameterTypes) {
                    if (genericParameterType instanceof ParameterizedType) {
                        System.out.println(genericParameterType.getTypeName());
                    }
                }
            }
        }
    }

    public <T> T test(List<String> l1, List<ArrayList<String>> l2, List<T> l3,
                      List<? extends Number> l4, List<ArrayList<String>[]> l5, Map<Boolean, Integer> l6) {
        return null;
    }
}

The output is as follows

java.util.List<java.lang.String>
java.util.List<java.util.ArrayList<java.lang.String>>
java.util.List<T>
java.util.List<? extends java.lang.Number>
java.util.List<java.util.ArrayList<java.lang.String>[]>
java.util.Map<java.lang.Boolean, java.lang.Integer>

Usage of GenericArrayType

GenericArrayType represents a generic array

public interface GenericArrayType extends Type {
    
    Type getGenericComponentType();
    
}

Create a generic class that contains a generic array

public class Holder<T> {

    private T[] arrayData;

    public void test(List<String>[] listArray, T[] values) {}

}
public class GenericTest {

    public static void main(String[] args) {
        Field[] fields = Holder.class.getDeclaredFields();
        for (Field field : fields) {
            if (field.getGenericType() instanceof GenericArrayType) {
                GenericArrayType genericType = (GenericArrayType) field.getGenericType();
                System.out.println(field.getName() + " is " + genericType.getTypeName() +
                        " and componentType is " + genericType.getGenericComponentType().getTypeName());
            }
        }
    }
}

The output is as follows

arrayData is T[] and componentType is T

Usage of TypeVariable

TypeVariable represents a generic variable. As long as the generic is not a specific type, such as < T >, < e extensions T >, they are classified as generic variables.

public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {
    // Get variable name
    String getName();
    // Get original type
    D getGenericDeclaration();
    // Gets the inheritance relationship of the generic type
    Type[] getBounds();
    AnnotatedType[] getAnnotatedBounds();
}

Gets the return parameter of the method

public class GenericTest {
    public static void main(String[] args) {
        Method[] methods = GenericTest.class.getDeclaredMethods();
        Method method = Arrays.stream(methods)
                .filter(v -> v.getName().equals("testTypeVariable"))
                .findAny().get();

        Type type = method.getGenericReturnType();
        displayType(type);
        TypeVariable typeVariable = (TypeVariable) type;
        System.out.println(typeVariable.getGenericDeclaration());
        displayType(typeVariable.getBounds()[0]);
    }

    public static void displayType(Type type) {
        System.out.println(type.getTypeName() + " --- " + type.getClass().getSimpleName());
    }

    public <T extends User> T testTypeVariable() {
        return null;
    }
}

The output is as follows

T --- TypeVariableImpl
public test.User test.Test.testTypeVariable()
test.User --- Class

Usage of WildcardType

WildcardType indicates wildcard, i.e. <? > This interface contains two methods

  • getUpperBounds: wildcard format, such as <? Extensions P > this indicates that the generic type inherits from P, and the getUpperBounds method can obtain the type of P
  • getLowerBounds: wildcard format, such as <? Super C > this indicates that the generic type is a superclass of C, and the getLowerBounds method can obtain the type of C
public interface WildcardType extends Type {
    // Upward inheritance
    Type[] getUpperBounds();
    // Downward inheritance
    Type[] getLowerBounds();
}

Get the parameters of the method and get the wildcard information defined in the parameters

public class GenericTest {

    public static void main(String[] args) {
        Method[] methods = GenericTest.class.getDeclaredMethods();
        Method method = Arrays.stream(methods)
                .filter(v -> v.getName().equals("testWildcardType"))
                .findAny().get();
        Type[] types = method.getGenericParameterTypes();
        int index = 0;
        for (Type type : types) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            Type typeArgument = parameterizedType.getActualTypeArguments()[0];
            System.out.println("parameter" + ++index + "Generic type:" + typeArgument.getClass().getSimpleName());

            if (typeArgument instanceof WildcardType) {
                WildcardType wildcardType = (WildcardType) typeArgument;
                for (Type upperType : wildcardType.getUpperBounds()) {
                    System.out.println("  upperType: " + upperType.getTypeName());
                }
                for (Type lowerType : wildcardType.getLowerBounds()) {
                    System.out.println("  lowerType: " + lowerType.getTypeName());
                }
            }
        }
    }

    public <T> void testWildcardType(List<T> l1, List<?> l2, List<? extends T> l3, List<? super Integer> l4) {
    }
}

The output is as follows

Parameter 1 generic type: TypeVariableImpl
 Parameter 2 generic type: WildcardTypeImpl
  upperType: java.lang.Object
 Parameter 3 generic type: WildcardTypeImpl
  upperType: T
 Parameter 4 generic type: WildcardTypeImpl
  upperType: java.lang.Object
  lowerType: java.lang.Integer

Topics: Java reflection