Artículos básicos: análisis en profundidad de los genéricos JAVA

1 sistema de tipos de JAVA

  • Primero comprenda el sistema de tipos de Java (clase de clase => tipo), el tipo es común a todos los tipos (tipo primitivo-Clase, tipo parametrizado-tipo parametrizado, tipo de matriz-GenericArrayType, tipo variable-TypeVariable, tipo básico-Clase) Interfaz; la Clase <T> mencionada en las dos primeras reflexiones y anotaciones es una clase de implementación de Tipo
  • Hay cuatro clases de subinterfaces en Type: ParameterizedType, TypeVariable, GenericArrayType, WildcardType
    • List <E> representa el tipo genérico, E es el tipo TypeVariable, List <String> es ParameterizedType (tipo parametrizado) , String in List <String> se llama tipo de parámetro real
    • Al reificar los tipos en genéricos, puede usar? Extends o? Super para indicar la relación de herencia; por ejemplo List<? extends Data>, el? En se llama el tipo comodín WildcardType
    • GenericArrayType representa un tipo de matriz cuyo tipo de elemento es ParameterizedType (tipo parametrizado) o TypeVariable (variable de tipo), como T [] o List <E> []
  • Las anotaciones solo aparecían en JDK1.5. Para indicar el tipo de anotado, se agrega el tipo AnnotatedElement, que literalmente significa el elemento anotado. JDK1.8 también tiene AnnotatedType para asociar Type con el concepto de elementos anotados.
  • AnnotatedType también tiene cuatro subinterfaces, que corresponden a las cuatro subinterfaces del Tipo uno a uno. Por ejemplo, si ParameterizedType está anotado, el compilador lo analizará en AnnotatedParameterizedType: @AnTest("list")List<String>list

2 El concepto de genéricos

  • Java generics (genéricos) es una nueva característica introducida en JDK1.5. Su esencia es un tipo parametrizado, que resuelve el problema de tipos de objetos específicos inciertos; el tipo de datos sobre el que opera se designa como un parámetro de tipo. Los tipos de parámetros se pueden utilizar en la creación de clases, interfaces y métodos, denominados respectivamente clases genéricas, interfaces genéricas y métodos genéricos.

Genéricos: difiera el trabajo de tipos claros hasta crear objetos o llamar a métodos para especificar tipos especiales

3 Ejemplos de clases genéricas y métodos genéricos

  • Definición de clase genérica
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();
  }
  • Definición de métodos genéricos
public class MainTest{
    
    
    public static void main(String[] args){
    
    
        printData("siting");
    }
    static  <T> T printData(T t){
    
    
        System.out.println(t);
        return t;
    }
}
  • Tanto las interfaces como las clases abstractas pueden usar genéricos

Borrado de 4 tipos

  • Al crear una instancia genérica, jvm borrará el tipo específico; el código de bytes compilado no contiene los parámetros de tipo en el genérico, es decir, ArrayList <String> y ArrayList <Integer> se borran a ArrayList, Que se borrará como "tipo nativo", es un borrado genérico
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();
    }
}

  • Compruebe cómo se representa el archivo de código de bytes compilado: menú de ideas -> ver -> mostrar 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
   ...
}
  • Se puede ver que T (String) se convierte al tipo de objeto, y la cadena inicializada inicial se ha ido

5 Herencia genérica

  • La subclase puede especificar los parámetros genéricos de la clase padre, que puede ser una clase conocida (Integer, String, etc.), o puede ser especificado por los propios parámetros genéricos de la subclase.
  • Cuando se hereda el tipo genérico y se especifica el parámetro genérico padre, el tipo ParameterizedType generado adicionalmente será el padre de la subclase; si no se especifica el parámetro genérico padre, el tipo original se heredará directamente
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 Variable genérica TypeVariable

  • (Primero defina un nombre temporalmente, E en Prueba <E> es un parámetro genérico); Variable genérica TypeVariable: el parámetro genérico del genérico es TypeVariable; cuando la clase padre usa el parámetro genérico de la subclase para especificar su propio parámetro genérico Cuando; o el atributo genérico se define en la clase genérica A <T>, y se usa el parámetro genérico T de la clase genérica A <T>, el compilador establecerá el parámetro genérico como la variable genérica TypeVariable en lugar de Borrado
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();
}
  • Cabe señalar que no podemos obtener directamente el tipo genérico de los parámetros específicos especificados, como los Class clazz = List<String>.classque no se pasan en tiempo de compilación; hay objetos creados directamente a través de la clase genérica new, y la clase no es el tipo ParameterizedType, sino el genérico en sí clase, el ejemplo es el siguiente
public class MainTest<T> {
    
    
    public static void main(String[] args){
    
    
        MainTest<String> str = new MainTest<String>();
        Class variable = str.getClass();
        Type genType1 = variable.getGenericSuperclass();
    }
}

  • Un tipo genérico que está específicamente parametrizado puede ser reconocido como un tipo ParameterizedType por el compilador. Hay tres formas de obtener el tipo ParameterizedType
// 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 comodín (WildcardType)

Comodín ilimitado: ¿Comodín ilimitado? Se puede adaptar a cualquier tipo de referencia:

  • Cuando un parámetro de método debe pasarse en un tipo genérico y su tipo no se puede determinar. Es probable que el uso directo de genéricos sin variables genéricas específicas cause riesgos de seguridad; si se realiza una conversión de tipo en el código del método, es muy probable que ocurran errores de ClassCastException
  • ¿No es suficiente reemplazar las variables genéricas con Object? Sin embargo, no existe una relación de herencia entre la clase genérica + el ParameterizedType de la transformación de parámetro específico; es decir, Object es la clase principal de String, pero los tipos de List <Object> y List <String> son dos ParameterizedTypes diferentes, que no existen Relación de herencia . Entonces, ¿hay comodines de tipo?
public static void print(List list){
    
    } 
----->>>
public static void print(List<?> list){
    
    } 

  • Los comodines ilimitados pueden coincidir con cualquier tipo, pero ¿en uso? En ese momento, no puede establecer el valor de la variable de la clase genérica, porque no sabemos cuál es el tipo específico; si el nuevo valor se establece a la fuerza, la lectura posterior es propensa a errores ClassCastException. Entonces, ¿el compilador restringe el comodín **? ** Genérico solo puede leer pero no escribir

Comodín de límite superior <? Extiende E>

  • Quiero recibir una colección List, que solo puede operar con elementos numéricos [Float, Integer, Double, Byte y otros tipos numéricos están bien]. ¿Cómo hacerlo? Se puede usar List<? extends Number的子类>, lo que indica que los elementos en List son todos subclases de Number
    public static void print(List<? extends Number> list) {
    
    
        Number n = new Double("1.0");
        list.add(n);
        Number tmp = list.get(0);
    }

  • Como se puede ver en la imagen, hay un comodín en el límite superior porque el tipo específico es incierto y solo se puede leer pero no escribir.

Comodín de límite inferior <? 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; }
}

  • Si se define el padre del comodín, es un comodín de límite inferior; este tipo de comodín es legible y escribible, y no se producirá ningún error ClassCastException cuando se convierta en cualquier padre.
  • Conjetura personal: ¿se debe a que la conversión descendente genérica de comodines y comodines de límite superior es propensa a errores de ClassCastException, mientras que la conversión de comodines de límite inferior no causará errores de ClassCastException, por lo que la especificación java restringe el primero a errores de compilación, pero el último compila?

9 Matriz genérica (GenericArrayType)

public interface GenericArrayType extends Type {
    //获得这个数组元素类型,即获得:A<T>(A<T>[])或  T(T[])
    Type getGenericComponentType();
}
  • GenericArrayType, matriz genérica, describe el tipo ParameterizedType y la matriz de tipo TypeVariable, es decir, la forma: Test <T> [] [], T [], etc., es una subinterfaz de 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();
    }
}


Bienvenida se refiere al error en el texto

Siga la cuenta pública, comuníquense juntos, busquen en WeChat: sigan adelante

Supongo que te gusta

Origin blog.csdn.net/u013591094/article/details/108960124
Recomendado
Clasificación