¿Qué es genérico en la tecnología Java Core?

¿Qué es genérico? Debemos haber visto T, K, V, etc. en el código de ingeniería, esto es genérico, luego veamos lo que dice el sitio web oficial @ 泛型 (Genérico)

Cuando saca un elemento de una colección, debe convertirlo en el tipo de elemento que está almacenado en la colección. Además de ser un inconveniente, esto no es seguro. El compilador no comprueba que su transmisión sea el mismo que el tipo de la colección, por lo que la transmisión puede fallar en tiempo de ejecución.
Generics proporciona una forma de comunicar el tipo de colección al compilador, para que pueda verificarse. Una vez que el compilador conoce el tipo de elemento de la colección, el compilador puede comprobar que ha utilizado la colección de forma coherente y puede insertar las conversiones correctas en los valores que se sacan de la colección.

Qué significa esta declaración oficial: cuando saca un elemento de la colección, debe convertirlo al tipo de elemento almacenado en la colección. Aparte de los inconvenientes, esto no es seguro. El compilador no comprueba si la conversión es del mismo tipo que la colección, por lo que la conversión puede fallar en tiempo de ejecución.
Los genéricos proporcionan una forma de pasar el tipo de una colección al compilador para que pueda comprobarse. Una vez que el compilador conoce el tipo de elemento de la colección, el compilador puede verificar si está utilizando la colección de manera consistente y puede insertar la coerción correcta para los valores tomados de la colección.

¿Qué significa este oscuro lenguaje oficial? En resumen, es una frase: la programación genérica significa que el código escrito puede ser reutilizado por muchos tipos diferentes de objetos.

Java Generic (Generic) es una nueva característica introducida en J2SE1.5. Su esencia es un tipo parametrizado , lo que significa que el tipo de datos que se manipula se designa como un parámetro (tipo parámetro). Este tipo de parámetro se puede utilizar en clases, En la creación de interfaces y métodos, se denominan clases genéricas, interfaces genéricas y métodos genéricos, respectivamente.

¿Cuál es el objetivo de aprender después de comprender el concepto genérico?

1. Comprender las reglas y el borrado de tipos de genéricos.
2. Comprender el tipo y limitar los comodines de dos genéricos.
3. Comprender la forma de usar genéricos en el diseño de API (clases genéricas personalizadas, interfaces genéricas, métodos genéricos)
4. Dominar el uso y los principios de los genéricos.
5. Dominar la aplicación de genéricos en middleware o frameworks de código abierto.

Discutamos estos temas uno por uno.

Reglas genéricas

Antes de JDK5.0, no existía el concepto de genéricos, entonces, ¿cómo escribiste el código en ese momento?

import java.io.File;
import java.util.ArrayList;
/**
 * @author mac
 * @date 2020/10/31-11:05
 */
public static void main(String[] args) {
   ArrayList arrayList = new ArrayList();
   arrayList.add(1);
   arrayList.add("a");
   // 这里没有错误检查。可以向数组列表中添加任何类的对象
   arrayList.add(new File("/"));
   // 对于这个调用,如果将get的结果强制类型转换为String类型,就会产生一个错误
   // Exception in thread "main" java.lang.ClassCastException: java.io.File cannot be cast to java.lang.String
   String file = (String) arrayList.get(2);
   System.out.println(file);
}

Antes de JDK5.0, si el valor de retorno de un método es Object y una colección contiene Object, entonces el valor o elemento de retorno solo se puede obtener mediante conversión forzada. Si hay un error de conversión de tipo, el compilador no puede detectarlo, lo que aumenta considerablemente ¡Probabilidad de error del programa!

public static void main(String[] args) {
    ArrayList<String> arrayList = new ArrayList<String>();
    arrayList.add("a");
    String s = (String) arrayList.get(0);
    // 6、7行代码编译不通过,不会导致运行后才发生错误
    arrayList.add(1);
    arrayList.add(new File("/"));
    String file = (String) arrayList.get(2);
}

Se puede ver en el uso de genéricos que los genéricos son un tipo de restricción, en resumen, los genéricos hacen tipos (clases e interfaces) como parámetros al definir clases, interfaces y métodos. Al igual que los parámetros formales más familiares que se utilizan en las declaraciones de métodos, los parámetros de tipo le brindan una forma de reutilizar el mismo código con diferentes entradas. La diferencia es que la entrada de un parámetro formal es un valor, mientras que la entrada de un parámetro de tipo es un tipo.
JDK comprueba los tipos en tiempo de compilación y proporciona seguridad de tipos en tiempo de compilación. Agrega seguridad de tipos en tiempo de compilación al marco de recopilación y elimina el trabajo pesado de conversión de tipos.

public class Person {
    int gender;
}
public class Driver extends Person {
    String name;
    int skilllevel;
}
public static void main(String[] ars) {
   List<Person> ls = new Arraylist<>();
   //这里会不会编译报错?
   List<Driver> list = ls;
}

Sin embargo, la aplicación de genéricos no está exenta de problemas, como el código anterior, se puede ver que el error de compilación, esta es una regla genérica que no permite el subtipo-asumiendo que está permitido, se puede cambiar a las siguientes condiciones, todo en el JDK la clase es una subclase de Objeto, si se le permite subclase de
tipo de, entonces ls no se puede almacenar en ningún tipo de elementos todavía, este tipo genérico limita y es completamente contrario, así que verifique JDK en genéricos Hay restricciones muy estrictas.

Para evitar la confusión de subtipos, los genéricos tienen el concepto de comodines

Comodines en genéricos

Comodín ilimitado

En el ejemplo genérico anterior, todos especificamos un tipo específico, al menos Objeto. Suponga que hay un escenario en el que no sabe qué es este tipo. Puede ser Objeto o Persona. Este tipo de escenario requiere el uso de comodines, como se
muestra a continuación, generalmente representados por un?.

public void addAll(Collection<?> col){
    ...
}

Comodín de límite superior

Basado en el escenario anterior, agregando que quiero restringir la subclase de este tipo a Persona, siempre que sea una subclase de Persona, si el tipo genérico está escrito como <Persona>, entonces solo se puede forzar como se muestra a continuación, entonces el tipo genérico se pierde El significado vuelve al punto de partida original. ¿Qué hacer en este momento?

List<Person> list = new ArrayList<>();
list.add(new Driver());
Person person = list.get(0);
Driver driver = (Driver) person; // 针对这种情况于是有了有界通配符的推出。

// 在泛型中指定上边界的叫上界通配符<? extends XXX>
public void count(Collection<? extends Person> persons) {
}
public void count2(Collection<Person> persons) {
}
public void testCount() {
    List<Driver> drivers = new ArrayList<>();
    // 符合上界通配符规则,编译不报错
    count(drivers);
    // 违反子类型化原则,编译报错
    count2(drivers);
    // 符合下界通配符原则,编译不报错
    List<Person> persons = new ArrayList<>();
}

Comodín inferior

El principio es el mismo que el comodín de límite superior. El comodín de límite inferior restringe el tipo desconocido a un tipo específico o al supertipo de ese tipo. El comodín de límite inferior utiliza un comodín ('?') Para indicar, seguido de la palabra clave super, seguido del límite inferior: <? super A>.

public void count3(Collection<? super Driver> drivers) {
}
public void testCount() {
    //符合下界通配符原则,编译不报错
    List<Person> persons = new ArrayList<>();
    count3(persons);
}

Métodos generales e inferencia de tipos

Método general

Método general significa que el tipo de parámetro del método es genérico, se pueden utilizar métodos tanto estáticos como no estáticos, y también se puede utilizar el método de construcción. Miramos el uso específico

/**
 * @author mac
 * @date 2020/10/31-12:24
 * 定义一个bean类
 */
public class Pair<K, V> {
    private K key; private V value;
    public Pair(K key, V value) {
        this.key = key; this.value = value;
    }
    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }
    public K getKey() { return key; }
    public V getValue() { return value; }
}

public class Util {
    // <K, V>通用方法入参类型
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
                p1.getValue().equals(p2.getValue()); // 使用Object中equals判断是否相等
    }
}

    public static void main(String[] args) {
        Pair<Integer, String> p1 = new Pair<>(1, "apple");
        Pair<Integer, String> p2 = new Pair<>(2, "pear");
        // JDK8之后可以这么写boolean same = Util.compare(p1, p2);
        boolean same = Util.<Integer, String>compare(p1, p2); 
        System.out.println(same); // false
    }

Inferencia de tipo

La inferencia de tipos es la capacidad del compilador de Java para observar cada llamada de método y la declaración correspondiente para determinar los parámetros de tipo que hacen que la llamada sea aplicable. El algoritmo de inferencia determina el tipo de parámetro y el tipo (si lo hay) que determina si el resultado ha sido asignado o devuelto. Finalmente, el algoritmo de inferencia intenta encontrar el tipo más específico para usar con todos los parámetros.

/**
 * @author macfmc
 * @date 2020/10/31-12:39
 */
public class Box<U> {
    U u;
    public U get() { return u; }
    public void set(U u) { this.u = u; }
}

public class BoxDemo {
    public static <U> void addBox(U u, List<Box<U>> boxes) {
        Box<U> box = new Box<U>();
        box.set(u);
        boxes.add(box);
    }
    public static <U> void outputBoxes(List<Box<U>> boxes) {
        int counter = 0;
        for (Box<U> box : boxes) {
            U boxContents = box.get();
            System.out.println("Box #" + counter + " contains [" + boxContents.toString() + "]");
            counter++;
        }
    }
    public static void main(String[] args) {
        ArrayList<Box<Integer>> listOfIntegerBoxes = new ArrayList<>();
        // JDK8可以使用 BoxDemo.addBox(Integer.valueOf(10), listOfIntegerBoxes);
        BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
        BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
        BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes);
        BoxDemo.outputBoxes(listOfIntegerBoxes);
    }
}
// 结果
// Box #0 contains [10]
// Box #1 contains [20]
// Box #2 contains [30]

Entonces entendemos el concepto, principio y uso de genéricos ¿Cómo se resuelven los genéricos en JVM?

Borrado genérico

Observamos las siguientes dos piezas de código

public class Node {
    private Object obj;
    public Object get() { return obj; }
    public void set(Object obj) { this.obj = obj; }
    public static void main(String[] argv) {
        Student stu = new Student();
        Node node = new Node();
        node.set(stu);
        Student stu2 = (Student) node.get();
    }
}

public class Node<T> {
    private T obj;
    public T get() { return obj; }
    public void set(T obj) { this.obj = obj; }
    public static void main(String[] argv) {
        Student stu = new Student();
        Node<Student> node = new Node<>();
        node.set(stu);
        Student stu2 = node.get();
    }
}

Lo compilamos por separado y vemos el archivo de código de bytes .class

    public Node();
        Code:
           0: aload_0
           1: invokespecial #1       // Method java/lang/Object."<init>": ()V
           4: return
    public java.lang.Object get();
        Code:
           0: aload_0
           1: getfield    #2        // Field obj:Ljava/lang/Object;
           4: areturn
    public void set(java.lang.Object);
        Code:
           0: aload_0
           1: aload_1
           2: putfield    #2        // Field obj:Ljava/lang/Object;
           5: return

    public Node();
        Code:
           0: aload_0
           1: invokespecial #1       // Method java/lang/Object."<init>": ()V
           4: return
    public java.lang.Object get();
        Code:
           0: aload_0
           1: getfield    #2        // Field obj:Ljava/lang/Object;
           4: areturn
    public void set(java.lang.Object);
        Code:
           0: aload_0
           1: aload_1
           2: putfield    #2        // Field obj:Ljava/lang/Object;
           5: return

Puede verse que los genéricos se utilizan para pasar información de tipo a códigos genéricos específicos cuando se utilizan. Después de la compilación, el archivo .class generado es exactamente el mismo que el código original, como si la información de tipo pasada se hubiera borrado.

El borrado de tipo incluye principalmente: 1. Eliminación de tipos generales: En el proceso de borrado de tipo, el compilador de Java borrará todos los parámetros de tipo, si los parámetros de tipo están delimitados, cada parámetro será reemplazado por el primero. Límite; si el parámetro de tipo es ilimitado, reemplácelo con Objeto. 2. El borrado de métodos generales: el compilador de Java también eliminará los parámetros de tipo en los parámetros del método general

Problema de eliminación de tipos

Método puente

La eliminación de tipos puede causar problemas inesperados en algunos casos.Para resolver este problema, el compilador de Java adopta un método de puente. Primer vistazo a un caso oficial

// 泛型擦除前
public class Node<T> {
    public T data;
    public Node(T data) { this.data = data; }
    public void setData(T data) { this.data = data; }
}
public class MyNode extends Node<Integer> {
    public MyNode(Integer data) { super(data); }
    public void setData(Integer data) { super.setData(data); }
}

// 泛型檫除后
public class Node {
    public Object data;
    public Node(Object data) { this.data = data; }
    public void setData(Object data) { this.data = data; }
}
public class MyNode extends Node {
    public MyNode(Integer data) { super(data); }
    public void setData(Integer data) { super.setData(data); }
}

// 但是编译器会产生桥接方法
public class MyNode extends Node {
    public MyNode(Object data) { super(data); }
    // Bridge method generated by the compiler
    // 编译器产生的桥接方法
    public void setData(Object data) { setData((Integer) data); }
    public void setData(Integer data) { super.setData(data); }
}

Contaminación del montón

La contaminación del montón no informará de un error en el tiempo de compilación, solo una advertencia que puede causar contaminación del montón en el tiempo de compilación. En tiempo de ejecución, si se produce la contaminación del montón, se lanzará una excepción de conversión de tipo. La contaminación de la pila (contaminación de la pila) se refiere a la posibilidad de contaminación de la pila cuando se asigna un objeto no genérico a una variable con un tipo genérico.

public static void main(String[] args) {
    List lists = new ArrayList<Integer>();
    lists.add(1);
    List<String> list = lists;
    // java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    String str = list.get(0);
    System.out.println(str);
}

Restricciones de tipo

El hecho de la conversión de genéricos de Java:
no hay genéricos en la máquina virtual, solo clases y métodos ordinarios.
Todos los parámetros de tipo se reemplazan por sus tipos calificados.
El método de puente se sintetiza para mantener el polimorfismo.
Para mantener la seguridad de los tipos, inserte conversiones coercitivas cuando sea necesario.

jdk define 7 tipos de restricciones de uso genérico:
1. No puede usar tipos simples para instanciar instancias genéricas
2. No puede crear directamente instancias de parámetros de tipo.
3. No puede declarar atributos estáticos como parámetros de tipo genérico.
4. No puede usar tipos parametrizados. Use cast o instanceof
5. No se puede crear un tipo genérico de matriz
6. No se pueden crear, capturar, lanzar objetos de tipo parametrizado
7. No se pueden tener dos métodos del mismo tipo primitivo en el método sobrecargado

1. No puede utilizar tipos simples para crear instancias genéricas

class Pair<K, V> {
    private K key;
    private V value;
    public Pair(K key, V value) { this.key = key; this.value = value; }
    public static void main(String[] args) {
        // 编译时会报错,因为 int、char 属于基础类型,不能用于实例化泛型对象
        Pair<int, char> p = new Pair(8, 'a');
        // 编译不会报错
        Pair<Integer, String> p2 = new Pair<>(8, "a");
    }
}

2. No se pueden crear instancias de parámetros de tipo directamente

    public static <E> void append(List<E> list) {
        E elem = new E(); // compile-time error 编译报错
        list.add(elem);
    }
    //作为解决办法,可以通过反射来创建
    public static <E> void append(List<E> list, Class<E> cls) throws Exception {
        E elem = cls.newInstance(); // OK
        list.add(elem);
    }

3. No se pueden declarar propiedades estáticas como parámetros de tipo genérico.

/**
 *  类的静态字段是该类所有非静态对象所共享的,如果可以,那么在有多种类型的情况下,os到底应该是哪种类型呢?
 *  下面这种情况,os到底应该是Smartphone还是Pager还是TablePC呢
 *  MobileDevice<Smartphone> phone = new MobileDevice<>();
 *  MobileDevice<Pager> pager = new MobileDevice<>();
 *  MobileDevice<TabletPC> pc = new MobileDevice<>();
 */
public class MobileDevice<T> {
    //非法
    private static T os;
}

4. No se puede utilizar cast o instanceof para tipos parametrizados

    public static <E> void rtti(List<E> list) {
        // 编译期会提示异常——因为 java 编译器在编译器会做类型檫除,于是在运行期就无法校验参数的类型
        if (list instanceof ArrayList<Integer>) { }
    }

    // 解决方法可以通过无界通配符来进行参数化
    public static void rtti(List<?> list) {
        // 编译不会报错
        if (list instanceof ArrayList<?>) { }
    }

5. No se pueden crear matrices genéricas

        // 编译器报错
        List<Integer>[] arrayOfLists = new List<Integer>[2];
        // 用一个通用列表尝试同样的事情,会出现一个问题
        Object[] strings = new String[2];
        strings[0] = "hi"; // OK
        strings[1] = 100; // An ArrayStoreException is thrown.
        Object[] stringLists = new List<String>[]; // compiler error, but pretend it's allowed 缺少数组维
        stringLists[0] = new ArrayList<String>(); // OK
        // java.lang.ArrayStoreException: java.util.ArrayList but the runtime can't detect it.
        stringLists[1] = new ArrayList<Integer>();

6. No se pueden crear, capturar ni lanzar objetos de tipo parametrizado

// 泛型类不能直接或间接的扩展 Throwable 类,以下情况会报编译错
// Extends Throwable indirectly
class MathException<T> extends Exception { } // compile-time error
// Extends Throwable directly
class QueueFullException<T> extends Throwable { } // compile-time error

// 捕捉泛型异常也是不允许的
    public static <T extends Exception, J> void execute(List<J> jobs) {
        try {
            for (J job : jobs) { }
        } catch (T e) { // compile-time error
        }
    }

// 但是可以在字句中使用类型参数
class Parser<T extends Exception> {
    public void parse(File file) throws T { }
}

7. No puede haber dos métodos del mismo tipo primitivo en el método sobrecargado

// 因为类型檫除后,两个方法将具有相同的签名,重载将共享相同的类文件表示形式,并且将生成编译时错误。
public class Example {
    public void print(Set<String> strSet) { }
    public void print(Set<Integer> intSet) { }
}

para resumir:

Se utilizará la evolución del código genérico y el uso de genéricos y por qué se utilizará la base, el uso genérico de tres tipos de reglas y el uso de comodines y el uso de métodos comunes y la inferencia de tipos se entenderá Avanzado y borrado de tipos borrar el tipo de problema y el tipo de restricción de uso se considera un suplemento familiar, puede comprender el enfoque de diseño genérico que se usa comúnmente API en el código fuente de JDK se considera competente.

 

 

Supongo que te gusta

Origin blog.csdn.net/FMC_WBL/article/details/109139475
Recomendado
Clasificación