In Java, generics and reflection are two important concepts that we can almost always use. And when it comes to Type, if anyone is unfamiliar with it, we should all understand that it is a direct implementation class, Class. Type is the common parent interface of all types in the Java language. This article is mainly about the other four subclasses of Type: ParameterizedType, TypeVariable, GenericArrayType and Wildcard Type. A reference for friends who want to know about these categories
-
ParameterizedType: Parametric Type
Parametric type is what we usually call generic type. When referring to parameters, the most familiar thing is to define a method with tangible parameters, and then pass arguments when calling the method. So how do you understand parameterized types? As the name implies, the type is parameterized from the original specific type, similar to the variable parameter in the method, in which case the type is also defined as a parameter form (which can be called a type parameter), and then the specific type (type parameter) is passed in when using/calling. So our ParameterizedType is such a type, let's look at its three important methods:
- getRawType(): Type
The purpose of this method is to return the type of the current ParameterizedType. For example, a List returns the Type of the List, that is, the Type of the current parameterized type itself.
-
getOwnerType(): Type
Returns the Type of the class in which the ParameterizedType type resides. For example, Map.Entry < String, Object > is the type of event Map returned by the parameterized type (because the class of Map.Entry is Map). - getActualTypeArguments(): Type[]
This method returns the actual parameter type in the parameterized type <>, such as Map < String, Person > map, which returns the String class and the Type Array of the fully qualified class name of Person class. Note: This method only returns the type in the outermost <>, no matter how many <> in the <>.
Let's use an example to illustrate the specific usage.
//It's Parameterized Type private HashMap<String, Object> map; private HashSet<String> set; private List<String> list; private Class<?> clz; //Not ParameterizedType private Integer i; private String str; private static void printParameterizedType(){ Field[] fields = TestParameterizedTypeBean.class.getDeclaredFields(); for (Field f : fields){ //Is printing a ParameterizedType type? System.out.println("FieldName: " + f.getName() + " instanceof ParameterizedType is : " + (f.getGenericType() instanceof ParameterizedType)); } //An array of actual parameter types in the map type getParameterizedTypeWithName("map"); getParameterizedTypeWithName("str"); } private static void getParameterizedTypeWithName(String name){ Field f; try { //Using reflection to get all variables in the TestParameterizedTypeBean class f = TestParameterizedTypeBean.class.getDeclaredField(name); f.setAccessible(true); Type type = f.getGenericType(); if (type instanceof ParameterizedType){ for(Type param : ((ParameterizedType)type).getActualTypeArguments()){ //Print the actual parameter type System.out.println("---type actualType---" + param.toString()); } //Types of the parent class in which to print System.out.println("---type ownerType0---"+ ((ParameterizedType) type).getOwnerType()); //Print its own type System.out.println("---type rawType---"+ ((ParameterizedType) type).getRawType()); } } catch (NoSuchFieldException e) { e.printStackTrace(); } }
The above code mainly defines some variables, among which are ParameterizedType and ordinary type variables. Let's take a look at the output of the above code:
-
TypeVariable: Type variable
Paradigm information is converted to a specific type at compilation time, and TypeVariable is used to reflect the information before the JVM compiles the generic type. (Generally speaking, TypeVariable is a generic variable like T, K, which we often use.)
- getBounds(): Type[]:
Returns the upper boundary of the current type, which is Object by default if no upper boundary is specified.
- getName(): String:
Returns the class name of the current type
- getGenericDeclaration(): D
Returns the Type of the class in which the current type is located.
The following is an example to deepen our understanding:
public class TestTypeVariableBean<K extends Number, T> { //K has a specified upper boundary Number K key; //T does not specify an upper boundary, which defaults to Object T value; public static void main(String[] args){ Type[] types = TestTypeVariableBean.class.getTypeParameters(); for (Type type : types){ TypeVariable t = (TypeVariable) type; int index = t.getBounds().length - 1; //Output Upper Boundary System.out.println("--getBounds()-- " + t.getBounds()[index]); //Output name System.out.println("--getName()--" + t.getName()); //Types of classes where the output is located System.out.println("--getGenericDeclaration()--" + t.getGenericDeclaration()); } } }
Look at the output again:
-
GenericArrayType: Generic array type:
The interface is implemented by generics among the elements that make up the array; its constituent elements are of the ParameterizedType or TypeVariable type. (In general, it's an array of parameter types. If it's just a parameterized type, it can't be called a generic array, but a parameterized type. Note: Regardless of how many [] are juxtaposed from left to right, the method simply removes the rest of the [] on the rightmost side as the return value of the method.
- getGenericComponentType(): Type:
Returns the actual parameterized type that makes up the generic array, such as List [].
Here's another example to get a better understanding of:
public class TestGenericArrayTypeBean<T> { //Generic array type private T[] value; private List<String>[] list; //Not a generic array type private List<String> singleList; private T singleValue; public static void main(String[] args){ Field[] fields = TestGenericArrayTypeBean.class.getDeclaredFields(); for (Field field: fields){ field.setAccessible(true); //Whether the output current variable is of GenericArrayType type System.out.println("Field: " + field.getName() + "; instanceof GenericArrayType" + ": " + (field.getGenericType() instanceof GenericArrayType)); if (field.getGenericType() instanceof GenericArrayType){ //If it is GenericArrayType, the current generic type is output System.out.println("Field: " + field.getName() + "; getGenericComponentType()" + ": " + (((GenericArrayType) field.getGenericType()).getGenericComponentType())); } } } }
Next, look at the output:
-
Wildcard Type: Wildcard Type
Represents wildcard types, such as <?>, <? Extends Number >, etc.
- getLowerBounds(): Type []: Gets an array of lower boundaries
- getUpperBounds(): Type []: Gets the type array of the upper boundary
Note: If no upper boundary is specified, the default is Object, and if no lower boundary is specified, the default is String.
Here is an example to illustrate:
public class TestWildcardType { public static void main(String[] args){ //Get all the methods of the TestWildcardType class (in this case, the testWildcardType method) Method[] methods = TestWildcardType.class.getDeclaredMethods(); for (Method method: methods){ //Get all the parameter types of the method Type[] types = method.getGenericParameterTypes(); for (Type paramsType: types){ System.out.println("type: " + paramsType.toString()); //If it is not a parameterized type, continue directly and execute the next loop condition if (!(paramsType instanceof ParameterizedType)){ continue; } //Strongly convert the current type to a parameterized type and obtain its actual parameter type (that is, generic type with wildcards) Type type = ((ParameterizedType) paramsType).getActualTypeArguments()[0]; //Output whether it is a wildcard type System.out.println("type instanceof WildcardType : " + ( type instanceof WildcardType)); if (type instanceof WildcardType){ int lowIndex = ((WildcardType) type).getLowerBounds().length - 1; int upperIndex = ((WildcardType) type).getUpperBounds().length - 1; //Output upper and lower boundaries System.out.println("getLowerBounds(): " + (lowIndex >= 0 ? ((WildcardType) type).getLowerBounds()[lowIndex] : "String ") + "; getUpperBounds(): " + (upperIndex >=0 ? ((WildcardType) type).getUpperBounds()[upperIndex]:"Object")); } } } } public void testWildcardType(List<? extends OutputStream> numberList, List<? super InputStream> upperList, List<Integer> list, InputStream inputStream){} }
Output:
-
Reasons for generic erasure and the role of Type in Java
In fact, before jdk1.5, Java had only primitive type but no generic type. After JDK 1.5, generic type was introduced, but this generic type only existed in compilation stage. When JVM was running, information related to generic type would be erased, such as List and List would be erased as List type at runtime. The reason for the existence of type erasure mechanism is that if generics exist at runtime, the JVM instruction set will be modified, which is very fatal.
In addition, the original type generates bytecode file objects, while the generic type-related type does not generate the corresponding bytecode file (because the generic type will be erased), so it is impossible to unify the new generic type with the class. Therefore, in order to expand the program and to reflect the operation of these types for the development needs, the type is introduced, and four new generic-related types, namely, Parameterized Type, TypeVariable, GenericArrayType, Wildcard Type, are added, together with Class, so that the parameters of Type type can be used to accept the parameters or return value types of the five seed classes mentioned above. Type type parameters. The generic-related types and primitive types Class are unified. In this way, we can also obtain generic type parameters by reflection.