Basic articles: In-depth analysis of JAVA generics

1 JAVA's Type system

  • First understand the Java Type type system (class of class => type), Type is common to all types (primitive type-Class, parameterized type-Parameterized type, array type-GenericArrayType, type variable-TypeVariable, basic type-Class) Interface; the Class<T> mentioned in the first two reflections and annotations is an implementation class of Type
  • There are four sub-interface classes under Type ParameterizedType, TypeVariable, GenericArrayType, WildcardType
    • List<E> represents generic type, E is TypeVariable type, List<String> is ParameterizedType (parameterized type) , String in List<String> is called actual parameter type
    • When reifying the types in generics, you can use? Extends or? Super to indicate the inheritance relationship; for example List<? extends Data>, the? In it is called the wildcard type WildcardType
    • GenericArrayType represents an array type whose element type is ParameterizedType (parameterized type) or TypeVariable (type variable), such as T[] or List<E>[]
  • Annotations only appeared in JDK 1.5. In order to express the annotated type, the AnnotatedElement type is added, which literally means the annotated element. JDK1.8 also has AnnotatedType to associate Type with the concept of annotated elements.
  • AnnotatedType also has four sub-interfaces, which correspond to the four sub-interfaces of Type one-to-one. For example, if ParameterizedType is annotated, it will be parsed into AnnotatedParameterizedType by the compiler: @AnTest("list")List<String>list

2 The concept of generics

  • Java generics (generics) is a new feature introduced in JDK1.5. Its essence is a parameterized type, which solves the problem of uncertain specific object types; the data type it operates on is designated as a type parameter. Parameter types can be used in the creation of classes, interfaces, and methods, respectively called generic classes, generic interfaces, and generic methods

Generics: Defer the work of clear types until creating objects or calling methods to specify special types

3 Examples of generic classes and generic methods

  • Definition of generic class
public class MainTest<T> {
    
    
    private  T param;
}
public static void main(String[] args){
    
    
        MainTest<String> data = new MainTest<String>(){
    
    };
        ParameterizedType genType1 = (ParameterizedType)data.getClass().getGenericSuperclass();
  }
  • Definition of generic methods
public class MainTest{
    
    
    public static void main(String[] args){
    
    
        printData("siting");
    }
    static  <T> T printData(T t){
    
    
        System.out.println(t);
        return t;
    }
}
  • Both interfaces and abstract classes can use generics

4 Type erasure

  • When creating a generic instance, jvm will erase the specific type; the compiled bytecode does not contain the type parameters in the generic, that is, ArrayList<String> and ArrayList<Integer> are erased to ArrayList, That is to be erased as "native type", this is generic erasure
public class MainTest {
    
    
    public static void main(String[] args){
    
    
        List<String> strArr  = new ArrayList<>();
        List<Integer> intArr  = new ArrayList<>();
        Type strClazz = strArr.getClass();
        Type intClazz = intArr.getClass();
    }
}

  • Check how the compiled bytecode file is represented: idea menu -> view -> show ByteCode
public class MainTest<T> {
    
    
    T param;
    public static void main(String[] args){
    
    
        MainTest<String> test = new MainTest<>();
        test.setParam("siting");
    }
    public T getParam() {
    
      return param;   }
    public void setParam(T param) {
    
      this.param = param;  }
}
public class com/MainTest {
    
    
  ...省略
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 7 L0
    NEW com/MainTest
    DUP
    INVOKESPECIAL com/MainTest.<init> ()V
    ASTORE 1
   L1
    LINENUMBER 8 L1
    ALOAD 1
    LDC "siting"     // 调用类型擦除后的setParam(Object)
    INVOKEVIRTUAL com/MainTest.setParam (Ljava/lang/Object;)V
   L2
   ...省略//getParam 的返回值是Object
  public getParam()Ljava/lang/Object;
   L0
    LINENUMBER 10 L0
    ALOAD 0
    GETFIELD com/MainTest.param : Ljava/lang/Object;
    ARETURN
   ...省略//setParam 的入参是Object
  public setParam(Ljava/lang/Object;)V
   L0
    LINENUMBER 11 L0
    ALOAD 0
    ALOAD 1
    PUTFIELD com/MainTest.param : Ljava/lang/Object;
    RETURN
   ...
}
  • It can be seen that T(String) is converted to Object type, and the initial initialized String is gone

5 Generic inheritance

  • The subclass can specify the generic parameters of the parent class, which can be a known class (Integer, String, etc.), or can be specified by the subclass’s own generic parameters
  • When the generic type is inherited and the parent generic parameter is specified, the additionally generated ParameterizedType type will be the parent of the subclass; if the parent generic parameter is not specified, the original type will be directly inherited
public class MainTest<T> {
    
    
    T param;
    static public class SubTest1 extends MainTest<String>{
    
    }
    static public class SubTest2<R> extends MainTest<R>{
    
    }
    //SubTest3继承的时原生类型
    static public class SubTest3 extends MainTest{
    
    }
}

6 Generic variable TypeVariable

  • (First define a name temporarily, E in Test<E> is a generic parameter); Generic variable TypeVariable: the generic parameter of the generic is TypeVariable; when the parent class uses the generic parameter of the subclass to specify its own generic parameter When; or the generic attribute is defined in the generic class A<T>, and the generic parameter T of the generic class A<T> is used, the generic parameter will be set as the generic variable TypeVariable by the compiler instead of Erased
public class MainTest<T> {
    
    
    List<T> param;
    public static void main(String[] args) throws Exception{
    
    
        Class clazz =  MainTest.class;
        TypeVariable[] typeVariable = clazz.getTypeParameters();
        // 1
        Field field = clazz.getDeclaredField("param");
        ParameterizedType arrayType = (ParameterizedType)field.getGenericType();
        // interface List<E> 的泛型类型E被T,具体化,因此其被识别为 TypeVariable
        TypeVariable variable1 = (TypeVariable)arrayType.getActualTypeArguments()[0];
        // 2
        ParameterizedType type = (ParameterizedType)SubTest.class.getGenericSuperclass();
        TypeVariable variable2 = (TypeVariable)type.getActualTypeArguments()[0];
    }
    static class SubTest<R> extends MainTest<R>{
    
    }
}

7 ParameterizedType

public interface ParameterizedType extends Type {
    
    
    //获取实际参数,List<String>里的String; 如果是List<T>则是TypeVariable类型
    Type[] getActualTypeArguments(); 
    // 获取原始类型List<String> -> List<E>
    Type getRawType();  
    Type getOwnerType();
}
  • It should be noted that we cannot directly obtain the generic type of the specified specific parameters, such as those Class clazz = List<String>.classthat are not passed at compile time; there are objects created directly through the generic class new, and the Class is not the ParameterizedType type, but the generic itself class, the example is as follows
public class MainTest<T> {
    
    
    public static void main(String[] args){
    
    
        MainTest<String> str = new MainTest<String>();
        Class variable = str.getClass();
        Type genType1 = variable.getGenericSuperclass();
    }
}

  • A generic type that is specifically parameterized can be recognized as a ParameterizedType type by the compiler. There are three ways to obtain the ParameterizedType type
// 1 子类继承泛型时,指定具体参数(可以是String等已知类型,也可以是子类的泛型参数)
// 2 获取在类内部定义的泛型属性,需指定具体泛型参数
// 3 局部代码,可以通过泛型的匿名内部子类(需指定具体泛型参数)获取ParameterizedType类型
public class MainTest<T> {
    
    
    List<T> list;
    public static void main(String[] args) throws NoSuchFieldException {
    
    
        SubTest<String> str = new SubTest<>();
        // 方式一
        Class variable = str.getClass();
        // 父类是(521)ParameterizedType类型
        ParameterizedType genType = (ParameterizedType)variable.getGenericSuperclass();
        // (521)ParameterizedType类型的原生类型是(479)class com.MainTest
        Type clazz = genType.getRawType();
        //MainTest.class 的原生类型是 (479)class com.MainTest
        Class rawClazz = MainTest.class;

        //方式二,泛型属性
        Field field = rawClazz.getDeclaredField("list");
        //属性list 类型是(546)ParameterizedType类型List<T>
        ParameterizedType fieldType = (ParameterizedType)field.getGenericType();

        // 方式三
        MainTest<String> sub3 = new MainTest<String>(){
    
    };
        // clazz3是匿名子类
        Class clazz3 =  sub3.getClass();
        //父类是(555)ParameterizedType类型
        ParameterizedType genType3 = (ParameterizedType) clazz3.getGenericSuperclass();
        // (555)ParameterizedType类型的原生类型是(479)class com.MainTest
        Type type3 = genType3.getRawType();
    }
    public static class SubTest<R> extends MainTest<R>{
    
     }
}

8 Wildcard (WildcardType)

Unbounded wildcard: Unbounded wildcard? Can be adapted to any reference type:

  • When a method parameter needs to be passed in a generic type, and its type cannot be determined. Direct use of generics without specific generic variables is likely to cause security risks; if type conversion is performed in the method code, ClassCastException errors are extremely likely to occur
  • Isn't it enough to replace generic variables with Object? However, there is no inheritance relationship between the generic class + the ParameterizedType of the specific parameter transformation; that is, Object is the parent class of String, but the types of List<Object> and List<String> are two different ParameterizedTypes, which do not exist Inheritance relationship . So there are type wildcards?
public static void print(List list){
    
    } 
----->>>
public static void print(List<?> list){
    
    } 

  • Unbounded wildcards can match any type; but in use? At the time, you cannot set the value of the variable of the generic class, because we don't know what the specific type is; if you forcibly set the new value, the subsequent reading is prone to ClassCastException errors. So the compiler restricts the ** wildcard? ** Generic can only read but not write

Upper bound wildcard <? Extends E>

  • I want to receive a List collection, which can only operate on numeric elements [Float, Integer, Double, Byte and other numeric types are fine]. How to do it? Can be used List<? extends Number的子类>, indicating that the elements in List are all subclasses of Number
    public static void print(List<? extends Number> list) {
    
    
        Number n = new Double("1.0");
        list.add(n);
        Number tmp = list.get(0);
    }

  • As can be seen in the picture, there is an upper bound wildcard, because the specific type is uncertain, and it can only be read but not written.

Lower bound wildcard <? super E>

class Parent{ }
class Child extends Parent{ }
public class MainTest<T> {
    T param;
    public static void main(String[] args){
        MainTest<? super Child> parent_m = new MainTest<>();
        parent_m.setParam(new Child());
        Object parent = parent_m.getParam();
    }
    public T getParam() {  return param;  }
    public void setParam(T param) {  this.param = param; }
}

  • If the parent of the wildcard is defined, it is a lower bound wildcard; this type of wildcard is readable and writable, and no ClassCastException error will occur when converted to any parent.
  • Personal guess: Is it because the generic down-casting of wildcards and upper-bounded wildcards is prone to ClassCastException errors, while the up- casting of lower-bounded wildcards will not cause ClassCastException errors, so the java specification restricts the former to compile errors, but the latter compiles through?

9 Generic Array (GenericArrayType)

public interface GenericArrayType extends Type {
    //获得这个数组元素类型,即获得:A<T>(A<T>[])或  T(T[])
    Type getGenericComponentType();
}
  • GenericArrayType, generic array, describes the ParameterizedType type and the TypeVariable type array, that is, the form: Test<T>[][], T[], etc., is a subinterface of GenericArrayType
public class MainTest<T> {
    
    
    T[] param;
    public static void main(String[] args) throws Exception{
    
    
        Class clazz =  MainTest.class;
        Field field = clazz.getDeclaredField("param");
        GenericArrayType arrayType = (GenericArrayType)field.getGenericType();
        TypeVariable variable = (TypeVariable) arrayType.getGenericComponentType();
    }
}


Welcome refers to the error in the text

Follow the public account, communicate together, search on WeChat: sneak forward

Guess you like

Origin blog.csdn.net/u013591094/article/details/108960124
Recommended