Explicación detallada de los genéricos de Java, la explicación gráfica y textual más completa de la historia

Los genéricos tienen una posición muy importante en Java, ya sea un marco de código abierto o código fuente JDK, se puede ver.

No es exagerado decir que los genéricos son un elemento esencial en el diseño general, por lo que es un curso obligatorio para comprender y utilizar los genéricos correctamente.

En primer lugar, la naturaleza de los genéricos.

Java generics (genéricos) es una característica nueva introducida en JDK 5. Los genéricos proporcionan un mecanismo de detección de seguridad de tipo en tiempo de compilación, que permite a los programadores detectar tipos ilegales en tiempo de compilación.

La esencia de los genéricos es parametrizar tipos , es decir, especificar un parámetro para el tipo y luego especificar el valor específico de este parámetro al usarlo, de modo que el tipo pueda determinarse cuando se use. Este tipo de parámetro se puede utilizar en clases, interfaces y métodos, que se denominan clases genéricas, interfaces genéricas y métodos genéricos, respectivamente.

2. Por qué usar genéricos

El beneficio de los genéricos es que la seguridad de tipos se verifica en el momento de la compilación y todas las conversiones son automáticas e implícitas, lo que mejora la reutilización del código.

(1) La seguridad del tipo está garantizada.

Antes de los genéricos, todos los objetos leídos de una colección tenían que convertirse, y si accidentalmente insertaba un objeto del tipo incorrecto, el proceso de conversión fallaba en el tiempo de ejecución.

Por ejemplo: usando colecciones sin genéricos:

public static void noGeneric() {

ArrayList names = new ArrayList();

names.add("mikechen的互联网架构");

names.add(123); //编译正常

}

 Usar colecciones con genéricos:

public static void useGeneric() {

ArrayList<String> names = new ArrayList<>();

names.add("mikechen的互联网架构");

names.add(123); //编译不通过

}

Con los genéricos, los nombres de conjuntos definidos no se compilarán cuando se compile add(123).

Es equivalente a decirle al compilador qué tipo de objetos recibe cada colección. El compilador realizará una verificación de tipos en el momento de la compilación para informar si se inserta un objeto del tipo incorrecto, lo que hace que el programa sea más seguro y mejora la solidez del programa.

(2) Eliminar la conversión forzada

Un beneficio adicional de los genéricos es que elimina muchas conversiones en el código fuente, lo que hace que el código sea más legible y reduce la posibilidad de errores.

Como ejemplo, el siguiente fragmento de código sin genéricos requiere conversión:

List list = new ArrayList();

list.add("hello");

String s = (String) list.get(0);

 Cuando se reescribe para usar genéricos, no es necesario convertir el código:

List<String> list = new ArrayList<String>();

list.add("hello");

String s = list.get(0); // no cast

(3) Evitar operaciones innecesarias de embalaje y desembalaje y mejorar el rendimiento del programa

En la programación no genérica, pasar un solo tipo como Objeto provoca operaciones de Boxing y Unboxing, las cuales son costosas. Después de la introducción de genéricos, no hay necesidad de realizar operaciones de Boxing y Unboxing, por lo que la eficiencia operativa es relativamente alta, especialmente en sistemas con frecuentes operaciones de recolección, la mejora del rendimiento que trae esta característica es más obvia.

Las variables genéricas tienen un tipo fijo, y cuando se usan, ya saben si es un tipo de valor o un tipo de referencia, evitando operaciones innecesarias de boxing y unboxing.

object a=1;//由于是object类型,会自动进行装箱操作。

int b=(int)a;//强制转换,拆箱操作。这样一去一来,当次数多了以后会影响程序的运行效率。

Después de usar genéricos

public static T GetValue<T>(T a)

{
  return a;
}

public static void Main()

{
  int b=GetValue<int>(1);//使用这个方法的时候已经指定了类型是int,所以不会有装箱和拆箱的操作。
}

(4) Mejorar la reutilización del código.

Tres: Cómo usar genéricos

Hay tres formas de usar genéricos: clases genéricas, interfaces genéricas y métodos genéricos.

1. Clase genérica

Clase genérica: definir genéricos en una clase

Formato de definición:

public class 类名 <泛型类型1,...> {  

}

Nota: Los tipos genéricos deben ser tipos de referencia (tipos de datos no primitivos)

Defina una clase genérica, agregue un par de corchetes angulares después del nombre de la clase y complete los parámetros de tipo en los corchetes angulares. Puede haber múltiples parámetros, y múltiples parámetros están separados por comas:

clase pública GenericClass<ab,a,c> {}

Por supuesto, el último tipo de parámetro también está estandarizado y no puede ser tan arbitrario como el anterior. Por lo general, usamos letras mayúsculas para los parámetros de tipo:

T:任意类型 type
E:集合中元素的类型 element
K:key-value形式 key
V:key-value形式 value
示例代码:

 Clase genérica:

public class GenericClass<T> {

    private T value;

    public GenericClass(T value) {

        this.value = value;
    }

    public T getValue() {

        return value;

    }

    public void setValue(T value) {

        this.value = value;

    }

}

Clase de prueba:

//TODO 1:泛型类

GenericClass<String> name = new GenericClass<>("mikechen的互联网架构");

System.out.println(name.getValue());

GenericClass<Integer> number = new GenericClass<>(123);

System.out.println(number.getValue());

resultado de la operación:

Descripción general de los métodos genéricos: definición de genéricos en métodos

Formato de definición:

public <泛型类型> 返回类型 方法名(泛型类型 变量名) {
  
}
  • Puntos a tener en cuenta:

    • Los parámetros formales definidos en la declaración del método solo se pueden usar en el método, mientras que los parámetros de tipo definidos en la interfaz y la declaración de clase se pueden usar en toda la interfaz y la clase. Al llamar al método fun(), de acuerdo con el objeto real pasado, el compilador determinará el tipo real representado por el parámetro de tipo T.

public interface GenericInterface<T> {

void show(T value);}

}

public class StringShowImpl implements GenericInterface<String> {

@Override

public void show(String value) {

System.out.println(value);

}}

public class NumberShowImpl implements GenericInterface<Integer> {

@Override

public void show(Integer value) {

System.out.println(value);

}}

Nota: cuando se utilizan genéricos, los tipos genéricos definidos antes y después deben ser coherentes; de lo contrario, se producirá una excepción de compilación:

GenericInterface<String> genericInterface = new NumberShowImpl();//编译异常

O simplemente no especifique el tipo, entonces cualquier tipo de nuevo está bien:

GenericInterface g1 = new NumberShowImpl();

GenericInterface g2 = new StringShowImpl();

3. Método genérico

El método genérico es para especificar el tipo específico del genérico al llamar al método.

  • Formato de definición:

modificador <variable que representa el tipo genérico> valor devuelto tipo método nombre (parámetro) { }

P.ej:

/**

     *

     * @param t 传入泛型的参数

     * @param <T> 泛型的类型

     * @return T 返回值为T类型

     * 说明:

     *   1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。

     *   2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。

     *   3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。

     *   4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E等形式的参数常用于表示泛型。

     */

    public <T> T genercMethod(T t){

        System.out.println(t.getClass());

        System.out.println(t);

        return t;

    }


public static void main(String[] args) {

    GenericsClassDemo<String> genericString  = new GenericsClassDemo("helloGeneric"); //这里的泛型跟下面调用的泛型方法可以不一样。

    String str = genericString.genercMethod("hello");//传入的是String类型,返回的也是String类型

    Integer i = genericString.genercMethod(123);//传入的是Integer类型,返回的也是Integer类型

}
 

class java.lang.String

hello
 
class java.lang.Integer

123

Se puede ver aquí que el método genérico obtiene diferentes tipos ya que nuestros tipos de parámetros entrantes son diferentes. Los métodos genéricos permiten que los métodos varíen independientemente de la clase.

Cuatro: comodines genéricos

El comodín de los genéricos de Java es una sintaxis especial que se utiliza para resolver el problema de pasar por referencia entre genéricos, principalmente en las siguientes tres categorías:

// 1:表示类型参数可以是任何类型

public class Apple<?>{} 

// 2:表示类型参数必须是A或者是A的子类

public class Apple<T extends A>{} 

// 3: 表示类型参数必须是A或者是A的超类型

public class Apple<T supers A>{}

1. Comodines ilimitados, que es <?>, como List<?>
La función principal de los comodines ilimitados es permitir que los genéricos acepten datos de tipos desconocidos.

2. Comodines con límite superior (Upper Bounded Wildcards), en forma de <? extends E>

Al usar genéricos comodín con límites superiores fijos, puede aceptar datos de la clase especificada y sus tipos de subclase.

Para declarar el uso de esta clase de comodines, use la forma de <? extends E>, donde E es el límite superior del tipo genérico.

Nota: Aunque aquí se usa la palabra clave extends, no se limita a las subclases que heredan la clase principal E, sino que también puede referirse a las clases que muestran la interfaz E.

3. Comodines de límite inferior (Lower Bounded Wildcards), en forma de <?super E>

Los genéricos comodín con un límite inferior fijo pueden aceptar datos de la clase especificada y sus tipos de superclase.

Para declarar el uso de esta clase de comodines, use la forma <?super E>, donde E es el límite inferior del tipo genérico.

Nota: Puede especificar límites superiores o inferiores para un genérico, pero no ambos.

Cinco: El significado de KTVE en genéricos

Si hacemos clic en el código fuente de algunas clases genéricas en el JDK, veremos los siguientes códigos:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    ...
}

public class HashMap<K,V> extends AbstractMap<K,V>    implements Map<K,V>, Cloneable, Serializable {
    ...
}    

¿Qué significan los parámetros genéricos E, K y V en las definiciones de clases genéricas anteriores?

De hecho, estos nombres de parámetros se pueden especificar de manera arbitraria, al igual que los nombres de parámetros de los métodos, pero generalmente damos un nombre significativo para que otros sepan lo que significa de un vistazo.

Los nombres de parámetros genéricos comunes son los siguientes:

E: Elemento (usado en colecciones, porque las colecciones almacenan elementos)
T: Tipo (clase Java)
K: Clave (clave)
V: Valor (valor)
N: Número (tipo numérico)
? : representa un tipo java indeterminado

Seis: el principio de realización de los genéricos

La esencia de los genéricos es parametrizar los tipos de datos, lo que se logra borrando, es decir, el compilador "borrará" la sintaxis genérica durante la compilación y realizará algunas acciones de conversión de tipos en consecuencia.

Debe ser claro mirar un ejemplo, como:​​​​​​​

public class Caculate<T> {
private T num;
}

Definimos una clase genérica, definimos un miembro de atributo, el tipo del miembro es un tipo genérico, no sabemos qué tipo de T es, solo se usa para calificar el tipo.

Descompilar esta clase de Caculate:​​​​​​​

public class Caculate{
public Caculate(){}
private Object num;
}

Se encontró que el compilador borró los dos corchetes angulares después de la clase Caculate y definió el tipo de num como de tipo Object.

Entonces, ¿se borran todos los tipos genéricos con Object? Los tipos genéricos se reemplazan con Object en la mayoría de los casos, pero no en un caso. Ese es un tipo acotado que usa la sintaxis extends y super, como:​​​​​​​

public class Caculate<T extends String> {
private T num;
}

Para tipos genéricos en este caso, num se reemplaza por String en lugar de Object.

Esta es una sintaxis calificada por tipo, que restringe T para que sea una subclase de String o String, es decir, cuando construye una instancia de Caculate, solo puede restringir T para que sea una subclase de String o String, por lo que no importa qué tipo de Si restringe, String es la clase principal, no habrá ningún problema de discrepancia de tipos, por lo que String se puede usar para borrar tipos.

De hecho, el compilador normalmente compilará y escribirá borrado donde se usen genéricos, y luego devolverá la instancia. Pero además de esto, si se utiliza la sintaxis genérica al crear una instancia genérica, el compilador marcará la instancia y prestará atención a todas las llamadas de método posteriores de la instancia, y realizará comprobaciones de seguridad antes de cada llamada.

De hecho, el compilador no solo presta atención a la invocación de un método genérico, sino que también realiza la conversión de tipos para algunos métodos cuyo valor de retorno es un tipo genérico calificado.Debido al borrado de tipos, los métodos cuyo valor de retorno es un tipo genérico serán Cuando se llama a estos métodos, el compilador insertará una línea adicional de instrucción de verificación para la conversión de tipos, este proceso se denomina "traducción genérica".

Siete: el último

Anteriormente, brindé una explicación completa y detallada de seis aspectos, desde la esencia de los genéricos de Java hasta el uso de los genéricos y el principio de implementación de los genéricos. ¡Espero que les sea útil!

Supongo que te gusta

Origin blog.csdn.net/qq_41701956/article/details/123473592
Recomendado
Clasificación