Type type in Java

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.

Keywords: Java jvm REST JDK

Added by drath on Tue, 14 May 2019 19:10:44 +0300