Comprensión de genéricos en Java y preguntas de entrevista relacionadas genéricas

1. ¿Por qué necesitas genéricos?
Primero mira el siguiente código:

List list = new ArrayList();
list.add("CSDN_SEU_Calvin");
list.add(100);
for (int i = 0; i < list.size(); i++) {
  String name = (String) list.get(i); //取出Integer时,运行时出现异常
  System.out.println("name:" + name);
}


Este ejemplo agrega un valor de tipo de cadena y un valor de tipo Integer a la colección de tipo de lista (esto es legal porque el tipo predeterminado de la lista en este momento es el tipo de objeto).
En el ciclo, debido a que se olvidó de agregar un valor entero u otras razones, se producirá una excepción java.lang.ClassCastException en tiempo de ejecución. Para resolver este problema, surgieron los genéricos.

2. El uso de la
programación genérica genérica de Java se introdujo después de JDK1.5. Los genéricos permiten a los programadores utilizar la abstracción de tipos, que normalmente se utiliza en colecciones .
Siempre que la primera línea de código en el ejemplo anterior se cambie al siguiente formulario, se informará un error al compilar list.add (100).
List <String> list = new ArrayList <String> ();
A través de List <String>, está directamente restringido que la colección de listas solo puede contener elementos de tipo String, por lo que en la sexta línea del ejemplo anterior, no es necesario realizar la conversión de tipos Debido a que la colección puede recordar la información de tipo de sus elementos, el compilador ha podido confirmar que es de tipo String.

3. Los genéricos solo son válidos en la etapa de compilación.Mire
el siguiente código:
ArrayList <String> a = new ArrayList <String> ();
ArrayList b = new ArrayList ();
Class c1 = a.getClass ();
Class c2 = b.getClass ( );
System.out.println (c1 == c2); // verdadero
El resultado de salida del programa anterior es verdadero. Debido a que todas las operaciones de reflexión están en tiempo de ejecución, ya que es cierto, prueba que después de la compilación, el programa tomará medidas desgenéricas.

En otras palabras, los genéricos en Java solo son válidos durante la fase de compilación. En el proceso de compilación, después de verificar correctamente el resultado genérico, se borrará la información relacionada con el genérico y se agregará el método de verificación de tipo y conversión de tipo en el límite del objeto que ingresa y sale del método. En otras palabras, el archivo de clase compilado correctamente no contiene información genérica.

La conclusión anterior se puede confirmar con el siguiente ejemplo de reflexión:

ArrayList<String> a = new ArrayList<String>();
a.add("CSDN_SEU_Calvin");
Class c = a.getClass();
try{
     Method method = c.getMethod("add",Object.class);
     method.invoke(a,100);

}catch(Exception e){
e.printStackTrace();
}System.out.println(a);


Debido a que omite la etapa de compilación y omite los genéricos, el resultado de salida es:
[CSDN_SEU_Calvin, 100] 4. Las clases genéricas y los métodos genéricos
son los siguientes. Veamos un ejemplo del uso de clases y métodos genéricos y compárelos con el uso de métodos no genéricos. Los resultados de los dos son los mismos. Publíquelos aquí para que los lectores comprendan las diferencias. . Si está interesado en ejemplos de interfaces genéricas, puede encontrar información, por lo que no entraré en detalles aquí.
(1) El caso del uso de genéricos

public static class FX<T> {
private T ob; // 定义泛型成员变量
public FX(T ob) {
this.ob = ob;
}
public T getOb() {
return ob;
}

public void showType() {
System.out.println("T的实际类型是: " + ob.getClass().getName());
}
}
public static void main(String[] args) {
FX<Integer> intOb = new FX<Integer>(100);
intOb.showType();
System.out.println("value= " + intOb.getOb());
System.out.println("----------------------------------");


FX<String> strOb = new FX<String>("CSDN_SEU_Calvin");
strOb.showType();
System.out.println("value= " + strOb.getOb());
}

(2) Cuando no use genéricos

public static class FX {
private Object ob; // 定义泛型成员变量

public FX(Object ob) {
this.ob = ob;
}
public Object getOb() {
return ob;
}

public void showType() {
System.out.println("T的实际类型是: " + ob.getClass().getName());
}
}
public static void main(String[] args) {
FX intOb = new FX(new Integer(100));
intOb.showType();
System.out.println("value= " + intOb.getOb());
System.out.println("----------------------------------");

FX strOb = new FX("CSDN_SEU_Calvin");
strOb.showType();
System.out.println("value= " + strOb.getOb());
}


Los resultados de salida de ambos métodos de escritura son:
El tipo real de T es: java.lang.Integer
value = 100
---------------------------- ------
El tipo real de T es: java.lang.String
value = CSDN_SEU_Calvin

5. Comodín
Para introducir el concepto de comodín, primero observe el siguiente código:

List<Integer> ex_int= new ArrayList<Integer>();  
List<Number> ex_num = ex_int; //非法的


Habrá un error de compilación en la línea 2 anterior, porque aunque Integer es una subclase de Number, List <Integer> no es una subclase de List <Number> .

Suponiendo que no hay ningún problema con la segunda línea de código, entonces podemos usar la instrucción ex_num.add (newDouble ()) para cargar varios tipos de subclases en una Lista. Esto obviamente no es posible, porque estamos sacando la lista de Cuando se trata del objeto, no está claro si debe convertirse a Integer o Double. Por lo tanto, necesitamos un tipo de referencia que pueda expresarse lógicamente como la clase principal de List <Integer> y List <Number> al mismo tiempo, y han surgido comodines de tipo. En este caso, se puede expresar como List <?>. El siguiente ejemplo también ilustra este punto, el comentario se ha escrito con mucha claridad.
 

public static void main(String[] args) {
FX<Number> ex_num = new FX<Number>(100);
FX<Integer> ex_int = new FX<Integer>(200);
getData(ex_num);
getData(ex_int);//编译错误
}
public static void getData(FX<Number> temp) { //此行若把Number换为“?”编译通过
//do something...
}

public static class FX<T> {
private T ob;
public FX(T ob) {
this.ob = ob;
}
}

6. Límite superior e inferior Puede
comprender que después de leer el ejemplo de límite superior a continuación, la forma del límite inferior FX <? Supers Number> no se repetirá.

public static void main(String[] args) {
FX<Number> ex_num = new FX<Number>(100);
FX<Integer> ex_int = new FX<Integer>(200);
getUpperNumberData(ex_num);
getUpperNumberData(ex_int);
}

public static void getUpperNumberData(FX<? extends Number> temp){
      System.out.println("class type :" + temp.getClass());
}

public static class FX<T> {
private T ob;
public FX(T ob) {
this.ob = ob;
}
}

7. Los beneficios de los genéricos
(1) Tipo de seguridad .
Al conocer las restricciones de tipo de variable definidas por los genéricos, el compilador puede mejorar de manera más eficaz la seguridad de tipos de los programas Java.
(2) Elimina la conversión de tipo forzada .
Elimina muchas conversiones en el código fuente. Esto hace que el código sea más legible y reduce la posibilidad de errores. Todas las conversiones forzadas son automáticas e implícitas.
(3) Mejorar el rendimiento .
Lits list1 = new ArrayList ();
list1.add ("CSDN_SEU_Calvin");
String str1 = (String) list1.get (0);

List <String> list2 = new ArrayList <String> ();
list2.add ("CSDN_SEU_Calvin");
String str2 = list2.get (0);
Para los dos programas anteriores, todo el trabajo debido a los genéricos está en el compilador Terminado, el código de bytes compilado por javac es el mismo (solo para garantizar la seguridad del tipo), entonces, ¿qué pasa con la mejora del rendimiento? Esto se debe a que en la implementación de genéricos, el compilador inserta la conversión de tipo forzada en el código de bytes generado, pero el hecho de que el compilador pueda utilizar más información de tipo hace posible optimizar versiones futuras de la JVM.

8. Precauciones para el uso de genéricos
(1) Los parámetros de tipo de genéricos solo pueden ser tipos de clase (incluidas las clases personalizadas), no tipos simples.
(2) Puede haber varios parámetros de tipo genérico.
(3) La operación instanceof no se puede utilizar en el tipo genérico exacto. Si las siguientes operaciones son ilegales, se producirán errores durante la compilación.

if (ex_num instancia de FX <Number>) {    
}
(4) No se puede crear una matriz de tipo genérico exacto . A continuación se utiliza un ejemplo de un documento de Sun para ilustrar este problema:
List <String> [] lsa = new List <String> [10]; // No realmente permitido.  
Object o = lsa;  
Object [] oa = (Object []) o;  
List <Integer> li = new ArrayList <Integer> ();  
li.add (new Integer (3));  
oa [1] = li; // Unsound, pero pasa la verificación de almacenamiento en tiempo de ejecución  
String s = lsa [1] .get (0); // Error en tiempo de ejecución: ClassCastException.  

En este caso, debido al mecanismo de borrado genérico de la JVM, la JVM no conoce la información genérica en tiempo de ejecución, por lo que se puede asignar un ArrayList <Integer> a oa [1] sin excepción, pero los datos se recuperan Cuando tenga que hacer una conversión de tipo, habrá ClassCastException, si se puede hacer la declaración de una matriz genérica, la situación mencionada anteriormente no aparecerá ninguna advertencia ni error en tiempo de compilación, y los errores solo ocurrirán en tiempo de ejecución .

Se permiten los siguientes comodines:
List <?> [] Lsa = new List <?> [10]; // OK, matriz de tipo comodín ilimitado.  
Object o = lsa;  
Object [] oa = (Object [ ]) o;  
List <Integer> li = new ArrayList <Integer> ();  
li.add (new Integer (3));  
oa [1] = li; // Correct.  
Integer i = (Integer) lsa [1] .get (0); // Aceptar

9. Lista y Lista <?>
(1) Lista es en realidad Lista <Objeto>. La lista en realidad representa una lista nativa que contiene cualquier tipo de objeto.
(2) Y List <?> Representa una List no nativa con un cierto tipo, pero no sabemos cuál es ese tipo.

Preguntas de entrevista relacionadas genéricas:

1. ¿Qué son los genéricos en Java? ¿Cuáles son los beneficios de usar genéricos? Los
genéricos son un mecanismo para parametrizar tipos. Puede hacer que el código sea aplicable a varios tipos, para escribir código más general, como el marco de colección.

Los genéricos son un mecanismo de confirmación de tipo en tiempo de compilación. Proporciona seguridad de tipos en tiempo de compilación, lo que garantiza que solo los objetos del tipo correcto se puedan usar en tipos genéricos (generalmente colecciones genéricas) y evita ClassCastException en tiempo de ejecución.

2. ¿Cómo funcionan los genéricos de Java? ¿Qué es el borrado de tipos?
El trabajo normal de los genéricos es confiar en el compilador para realizar la verificación de tipos al compilar el código fuente, luego escribir borrado e insertar donde aparecen los parámetros de tipo. Se realizan las instrucciones relevantes de conversión forzada.

El compilador borra toda la información relacionada con el tipo en tiempo de compilación, por lo que no hay información relacionada con el tipo en tiempo de ejecución. Por ejemplo, List <String> está representado por un solo tipo de List en tiempo de ejecución. ¿Por qué quieres borrarlo? Esto es para evitar la expansión de tipos .

3. ¿Qué son los comodines calificados y los comodines no calificados en genéricos? Los
comodines calificados restringen el tipo. Hay dos comodines calificados, uno es <? Extiende T>, que establece el límite superior del tipo asegurando que el tipo debe ser una subclase de T, y el otro es <? Super T>, que asegura que el tipo debe ser el padre de T Clase para establecer el límite inferior del tipo. El tipo genérico debe inicializarse con el tipo dentro del límite, de lo contrario provocará un error de compilación. Por otro lado, <?> Representa un comodín no calificado, porque <?> Puede ser reemplazado por cualquier tipo.

4. ¿Cuál es la diferencia entre Lista <? Extiende T> y Lista <? Super T>?
Esto está relacionado con la última pregunta de la entrevista. A veces, el entrevistador utilizará esta pregunta para evaluar su comprensión de los genéricos en lugar de preguntar directamente ¿Qué son los comodines calificados y los comodines no calificados? Estas dos declaraciones List son ejemplos de comodines calificados. List <? Extiende T> puede aceptar cualquier List heredado de T, y List <? Super T> puede aceptar cualquier List formado por la clase padre de T. Por ejemplo, List <? Extiende Número> puede aceptar List <Integer> o List <Float>. Se puede encontrar más información en los enlaces que aparecen en este párrafo.

5. ¿Cómo escribir un método genérico para que pueda aceptar parámetros genéricos y devolver tipos genéricos? No
es difícil escribir métodos genéricos. Necesita usar tipos genéricos en lugar de tipos primitivos, como usar T, E o K, V Y otros marcadores de posición de tipo ampliamente reconocidos. Para obtener ejemplos de métodos genéricos, consulte Java Collection Framework. En el caso más simple, un método genérico podría verse así:
public V put (clave K, valor V) {  
    return cache.put (clave, valor);  
}  

6. ¿Cómo usar genéricos para escribir clases con parámetros en Java?
Esta es una extensión de la pregunta de la entrevista anterior. El entrevistador puede pedirle que escriba una clase de tipo seguro utilizando genéricos en lugar de escribir un método genérico. La clave sigue siendo utilizar tipos genéricos en lugar de tipos primitivos, y utilizar los marcadores de posición estándar utilizados en el JDK.

7. ¿Escribir un programa genérico para implementar el almacenamiento en caché LRU?
Esto es equivalente a un ejercicio para aquellos a los que les gusta la programación Java. Para darle una pista, LinkedHashMap se puede utilizar para implementar una caché LRU de tamaño fijo. Cuando la caché LRU está llena, eliminará el par clave-valor más antiguo de la caché. LinkedHashMap proporciona un método llamado removeEldestEntry (), que será llamado por put () y putAll () para eliminar el par clave-valor más antiguo.

package com.java.oop.features;
import java.util.LinkedHashMap;
//构建基于LRU算法的缓存对象(简易)
//缓存满了要淘汰长时间不访问的对象
class LruCache<K,V> extends LinkedHashMap<K,V>{
	  LinkedHashMap<K,V> removeElements=
			new LinkedHashMap<K, V>();//放移除的对象
	  private int maxCap;//记录最大容量
	  public LruCache(int cap) {
		super((int)Math.ceil(cap/0.75f)+1,0.75f,true);//调用父类有参构造
	    this.maxCap=cap;
	  }
	  //当我们执行put方法时,每次都会调用此方法
	  //方法返回值为true时表示满了,此时可以移除数据
	  @Override
	  protected boolean removeEldestEntry(
		java.util.Map.Entry<K, V> eldest) {
		boolean flag= size()>maxCap;
		if(flag) {
			removeElements.put(eldest.getKey(), eldest.getValue());
		}
		return flag;
	  }
}
public class TestExtends02 {
   public static void main(String[] args) {
	  LruCache<String,Object> cache=
	  new LruCache<>(3);
      cache.put("A", 100);
      cache.put("B", 200);
      cache.put("C", 300);
      cache.get("A");
      cache.put("D", 400);
      cache.put("E", 500);
      System.out.println(cache);
      System.out.println(cache.removeElements);
   } 
}

Resultado de salida:

{A=100, D=400, E=500}
{B=200, C=300}

8. ¿Puede pasar List <String> a un método que acepte los parámetros List <Object>?
Para cualquiera que no esté familiarizado con los genéricos, este tema de genéricos de Java parece confuso, porque a primera vista String es un tipo de Objeto, por lo que List <String> debe usarse donde se necesita List <Object> ,Pero ese no es el caso. Hacerlo provocará errores de compilación. Si lo piensa más, encontrará que Java tiene sentido para hacer esto, porque List <Object> puede almacenar cualquier tipo de objeto, incluyendo String, Integer, etc., mientras que List <String> solo se puede usar para almacenar Strings.
 

List<Object> objectList;  
List<String> stringList;   
objectList = stringList;  //compilation error incompatible types  

9. ¿Se pueden usar genéricos en Array?
Esta es probablemente la más simple de las preguntas de la entrevista sobre genéricos de Java, por supuesto, la premisa es que debe saber que Array no admite genéricos de hecho , por lo que Joshua Bloch en el libro Effective Java Se recomienda utilizar List en lugar de Array, porque List puede proporcionar garantías de seguridad de tipo en tiempo de compilación, pero Array no.

10. ¿Cómo evitar las advertencias sin marcar en Java?
Si mezcla tipos genéricos y primitivos, como el siguiente código, el compilador javac de Java 5 generará advertencias sin marcar, como List <String> rawList = new ArrayList ()
Nota: Hello.java usa operaciones no marcadas o inseguras;
tales advertencias pueden protegerse con la anotación @SuppressWarnings ("sin marcar").

11. ¿Cuál es la diferencia entre List <Object> y List de tipo primitivo en Java?
La principal diferencia entre el tipo de primitive y el tipo de parámetro <Object> es que el compilador no realizará verificaciones de seguridad de tipos en tipos primitivos en tiempo de compilación, pero Verifique el tipo con parámetros . Al usar Object como tipo, puede decirle al compilador que el método puede aceptar cualquier tipo de objeto, como String o Integer. El foco de esta pregunta radica en la comprensión correcta de los tipos primitivos en genéricos. La segunda diferencia entre ellos es que puede pasar cualquier tipo genérico con parámetros al método que acepta el tipo original List, pero no puede pasar List <String> al método que acepta List <Object>, porque producirá Error de compilación.
12. ¿Cuál es la diferencia entre List <?> Y List <Object> en Java? Esta
pregunta es muy similar a la pregunta anterior, pero es completamente diferente en esencia. List <?> Es una lista de tipo desconocido y List <Object> es en realidad una lista de cualquier tipo. Puede asignar List <String>, List <Integer> a List <?>, Pero no puede asignar List <String> a List <Object>.   

List<?> listOfAnyType;  
List<Object> listOfObject = new ArrayList<Object>();  
List<String> listOfString = new ArrayList<String>();  
List<Integer> listOfInteger = new ArrayList<Integer>();  
        
listOfAnyType = listOfString; //legal  
listOfAnyType = listOfInteger; //legal  

listOfObjectType = (List<Object>) listOfString; //compiler error - in-convertible types  


13. La diferencia entre List <String> y el tipo primitivo List.
Esta pregunta es similar a "¿Cuál es la diferencia entre el tipo primitivo y el tipo de parámetro?". El tipo de parámetro es de tipo seguro y el compilador garantiza su tipo de seguridad , pero la primitiva Lista de tipos no es de tipo seguro. No puede almacenar ningún tipo de Objeto que no sea String en la Lista de tipos de String, pero puede almacenar cualquier tipo de objeto en la Lista original. No es necesario utilizar un tipo genérico con parámetros para la conversión de tipos, pero para los tipos primitivos, debe realizar una conversión de tipos explícita.

 

List listOfRawTypes = new ArrayList();  
listOfRawTypes.add("abc");  
listOfRawTypes.add(123); //编译器允许这样 - 运行时却会出现异常  
String item = (String) listOfRawTypes.get(0); //需要显式的类型转换  
item = (String) listOfRawTypes.get(1); //抛ClassCastException,因为Integer不能被转换为String  
        
List<String> listOfString = new ArrayList();  
listOfString.add("abcd");  
listOfString.add(1234); //编译错误,比在运行时抛异常要好  
item = listOfString.get(0); //不需要显式的类型转换 - 编译器自动转换  

 

Esta publicación de blog fue compilada por Xiaobai sobre los hombros del Gran Dios. Agradezco sinceramente a los grandes dioses su dedicación desinteresada y su espíritu generoso. Esta cualidad nos ha traído una gran ayuda para el crecimiento técnico de Xiaobai.
Referencia:
https://blog.csdn.net/seu_calvin/article/details/52230032
https://blog.csdn.net/sunxianghuang/article/details/51982979
—————————————— -
Descargo de responsabilidad: este artículo es el artículo original del blogger de CSDN "diez frijoles tres Exposición", y sigue el acuerdo de derechos de autor CC 4.0 BY-SA, reproducido, adjunte el enlace de la fuente original y esta declaración.
Enlace original: https://blog.csdn.net/zz13995900221/article/details/79736057

Supongo que te gusta

Origin blog.csdn.net/qianzhitu/article/details/102995570
Recomendado
Clasificación