Compréhension des génériques en Java et des questions d'entrevue génériques liées

1. Pourquoi avez-vous besoin de génériques?
Regardez d'abord le code suivant:

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);
}


Cet exemple ajoute une valeur de type chaîne et une valeur de type Integer à la collection de types de liste (cela est légal car le type par défaut de la liste à ce stade est le type Object).
Dans la boucle, en raison de l'oubli d'ajouter une valeur Integer ou pour d'autres raisons, java.lang.ClassCastException se produira au moment de l'exécution. Pour résoudre ce problème, des génériques ont vu le jour.

2. L'utilisation de la programmation
générique Java générique a été introduite après JDK1.5. Les génériques permettent aux programmeurs d'utiliser l'abstraction de type, généralement utilisée dans les collections .
Tant que la première ligne de code de l'exemple ci-dessus est remplacée par la forme suivante, une erreur sera signalée lors de la compilation de list.add (100).
List <String> list = new ArrayList <String> ();
Grâce à List <String>, il est directement limité que la collection de listes ne puisse contenir que des éléments de type String, de sorte que dans la 6ème ligne de l'exemple ci-dessus, il n'est pas nécessaire d'effectuer une conversion de type , Étant donné que la collection peut se souvenir des informations de type de ses éléments, le compilateur a pu confirmer qu'il était de type String.

3. Les génériques ne sont valides qu'au stade de la compilation.
Regardez le code suivant:
ArrayList <String> a = new ArrayList <String> ();
ArrayList b = new ArrayList ();
Class c1 = a.getClass ();
Class c2 = b.getClass ( );
System.out.println (c1 == c2); // true
Le résultat de sortie du programme ci-dessus est true. Parce que toutes les opérations de réflexion sont à l'exécution, puisque c'est vrai, cela prouve qu'après la compilation, le programme prendra des mesures dé-génériques.

En d'autres termes, les génériques en Java ne sont valides que pendant la phase de compilation. Dans le processus de compilation, après avoir correctement vérifié le résultat générique, les informations associées du générique seront effacées, et la méthode de vérification de type et de conversion de type sera ajoutée à la limite de l'objet entrant et sortant de la méthode. En d'autres termes, le fichier de classe compilé avec succès ne contient aucune information générique.

La conclusion ci-dessus peut être confirmée par l'exemple de réflexion suivant:

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);


Comme il contourne la phase de compilation, les génériques sont contournés et la sortie est:
[CSDN_SEU_Calvin, 100] 4. Les classes génériques et les méthodes génériques
sont les suivantes. Examinons un exemple d'utilisation de classes et de méthodes génériques et comparons-les à l'utilisation de méthodes non génériques. Les résultats de sortie des deux sont identiques. Postez-les ici pour que les lecteurs comprennent les différences. . Si vous êtes intéressé par des exemples d'interface génériques, vous pouvez trouver des informations, donc je n'entrerai pas dans les détails ici.
(1) Le cas de l'utilisation de génériques

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) Le cas de la non-utilisation de génériques

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());
}


Les résultats de sortie des deux méthodes d'écriture sont:
Le type réel de T est: java.lang.Integer
value = 100
---------------------------- ------
Le type réel de T est: java.lang.String
value = CSDN_SEU_Calvin

5. Wildcard
Afin d'introduire le concept de wildcard, regardez d'abord le code suivant:

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


Il y aura une erreur de compilation à la ligne 2 ci-dessus, car bien que Integer soit une sous-classe de Number, List <Integer> n'est pas une sous-classe de List <Number> .

En supposant qu'il n'y a pas de problème avec la deuxième ligne de code, alors nous pouvons utiliser l'instruction ex_num.add (newDouble ()) pour charger différents types de sous-classes dans une liste. Ce n'est évidemment pas possible, car nous supprimons la liste des En ce qui concerne les objets, il n'est pas clair s'il faut convertir en Integer ou Double. Par conséquent, nous avons besoin d'un type de référence qui peut être logiquement exprimé en tant que classe parente de List <Integer> et List <Number> en même temps, et des caractères génériques de type sont apparus. Dans ce cas, il peut être exprimé par List <?>. L'exemple suivant illustre également ce point, le commentaire a été rédigé très clairement.
 

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. Limite supérieure et inférieure Vous
pouvez comprendre après avoir lu l'exemple de limite supérieure ci-dessous, la forme de la limite inférieure FX <? Supers Number> ne sera pas répétée.

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. Les avantages des génériques
(1) Sécurité de type .
En connaissant les restrictions de type de variable définies par les génériques, le compilateur peut améliorer plus efficacement la sécurité de type des programmes Java.
(2) Éliminer la conversion de type forcée .
Éliminez de nombreux casts dans le code source. Cela rend le code plus lisible et réduit le risque d'erreurs. Tous les casts sont automatiques et implicites.
(3) Améliorez les performances .
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);
Pour les deux programmes ci-dessus, tout le travail dû aux génériques est dans le compilateur Terminé, le bytecode compilé par javac est le même (juste pour assurer la sécurité du type), alors qu'en est-il de l'amélioration des performances? C'est parce que dans l'implémentation des génériques, le compilateur insère une conversion de type forcée dans le bytecode généré, mais le fait que plus d'informations de type puissent être utilisées par le compilateur permet d'optimiser les futures versions de la JVM.

8. Précautions pour l'utilisation des génériques
(1) Les paramètres de type des génériques ne peuvent être que des types de classe (y compris des classes personnalisées), pas des types simples.
(2) Il peut y avoir plusieurs paramètres de type générique.
(3) L'opération instanceof ne peut pas être utilisée sur le type générique exact. Si les opérations suivantes sont illégales, des erreurs se produiront lors de la compilation.

if (ex_num instanceof FX <Number>) {    
}
(4) Impossible de créer un tableau de type générique exact . L'exemple suivant utilise un exemple d'un document Sun pour illustrer ce problème:
List <String> [] lsa = new List <String> [10]; // Pas vraiment autorisé.  
Object o = lsa;  
Object [] oa = (Object []) o;  
List <Integer> li = new ArrayList <Integer> ();  
li.add (new Integer (3));  
oa [1] = li; // Non sonore, mais passe la vérification du magasin à l'exécution  
String s = lsa [1] .get (0); // Erreur d'exécution: ClassCastException.  

Dans ce cas, en raison du mécanisme d'effacement générique de la JVM, la JVM ne connaît pas les informations génériques au moment de l'exécution, donc un ArrayList <Integer> peut être affecté à oa [1] sans exception, mais les données sont récupérées Lorsque vous devez effectuer une conversion de type, il y aura donc ClassCastException, si la déclaration d'un tableau générique peut être faite, la situation mentionnée ci-dessus n'apparaîtra pas d'avertissements et d'erreurs au moment de la compilation, et les erreurs ne se produiront qu'au moment de l'exécution .

Les caractères génériques suivants sont autorisés:
List <?> [] Lsa = new List <?> [10]; // OK, tableau de type générique illimité.  
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); // OK

9. List and List <?>
(1) List est en fait List <Object>. List représente en fait une liste native contenant n'importe quel type d'objet.
(2) Et List <?> Représente une List non native avec un certain type, mais nous ne savons pas quel est ce type.

Questions d'entrevue génériques liées:

1. Que sont les génériques en Java Quels sont les avantages de l'utilisation des génériques Les
génériques sont un mécanisme de paramétrage des types. Il peut rendre le code applicable à différents types, afin d'écrire du code plus général, tel que le framework de collection.

Les génériques sont un mécanisme de confirmation de type à la compilation. Il fournit une sécurité de type au moment de la compilation, garantissant que seuls les objets du type correct peuvent être utilisés sur des types génériques (généralement des collections génériques) et évitant ClassCastException au moment de l'exécution.

2. Comment fonctionnent les génériques de Java? Qu'est-ce que l'effacement de type?
Le travail normal des génériques consiste à compter sur le compilateur pour effectuer d'abord la vérification de type lors de la compilation du code source, puis à effacer le type et à insérer là où les paramètres de type apparaissent Les instructions pertinentes de conversion forcée sont réalisées.

Le compilateur efface toutes les informations relatives au type au moment de la compilation, il n'y a donc aucune information relative au type au moment de l'exécution. Par exemple, List <String> est représenté par un seul type de liste au moment de l'exécution. Pourquoi voulez-vous l'effacer? Ceci afin d' éviter l'expansion de type .

3. Quels sont les caractères génériques qualifiés et les caractères génériques non qualifiés dans les génériques? Les
caractères génériques qualifiés restreignent le type. Il existe deux caractères génériques qualifiés, l'un est <? Extend T>, qui définit la limite supérieure du type en s'assurant que le type doit être une sous-classe de T, et l'autre est <? Super T>, ce qui garantit que le type doit être le parent de T Classe pour définir la limite inférieure du type. Le type générique doit être initialisé avec le type dans la limite, sinon cela provoquera une erreur de compilation. D'un autre côté, <?> Représente un caractère générique non qualifié, car <?> Peut être remplacé par n'importe quel type.

4. Quelle est la différence entre List <? Extend T> et List <? Super T>?
Ceci est lié à la question de l'entrevue précédente. Parfois, les enquêteurs utiliseront cette question pour évaluer votre compréhension des génériques au lieu de poser directement Quels sont vos caractères génériques qualifiés et caractères génériques non qualifiés. Ces deux déclarations List sont des exemples de caractères génériques qualifiés. List <? Extend T> peut accepter n'importe quelle List héritée de T et List <? Super T> peut accepter n'importe quelle List formée par la classe parente de T. Par exemple, List <? Extend Number> peut accepter List <Integer> ou List <Float>. Plus d'informations peuvent être trouvées dans les liens qui apparaissent dans ce paragraphe.

5. Comment écrire une méthode générique pour qu'elle puisse accepter des paramètres génériques et renvoyer des types génériques? Il
n'est pas difficile d'écrire des méthodes génériques. Vous devez utiliser des types génériques au lieu de types primitifs, comme l'utilisation de T, E ou K, V Et d'autres espaces réservés de type largement reconnus. Pour obtenir des exemples de méthodes génériques, reportez-vous à Java Collection Framework. Dans le cas le plus simple, une méthode générique pourrait ressembler à ceci:
public V put (K key, V value) {  
    return cache.put (key, value);  
}  

6. Comment utiliser les génériques pour écrire des classes avec des paramètres en Java?
Ceci est une extension de la question d'entretien précédente. L'enquêteur peut vous demander d'écrire une classe de type sécurisé avec des génériques au lieu d'écrire une méthode générique. La clé est toujours d'utiliser des types génériques au lieu de types primitifs et d'utiliser les espaces réservés standard utilisés dans le JDK.

7. Écrire un programme générique pour implémenter la mise en cache LRU?
Ceci équivaut à un exercice pour ceux qui aiment la programmation Java. Pour vous donner un indice, LinkedHashMap peut être utilisé pour implémenter un cache LRU de taille fixe. Lorsque le cache LRU est plein, il supprimera la paire clé-valeur la plus ancienne du cache. LinkedHashMap fournit une méthode appelée removeEldestEntry (), qui sera appelée par put () et putAll () pour supprimer la plus ancienne paire clé-valeur.

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);
   } 
}

Résultat de sortie:

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

8. Pouvez-vous transmettre List <String> à une méthode qui accepte les paramètres List <Object>?
Pour tous ceux qui ne sont pas familiarisés avec les génériques, cette rubrique sur les génériques Java semble déroutante, car à première vue, String est une sorte d'objet, donc List <String> doit être utilisé là où List <Object> est nécessaire ,Mais ce n'est pas le cas. Cela entraînera des erreurs de compilation. Si vous y réfléchissez plus loin, vous constaterez que Java a du sens pour le faire, car List <Object> peut stocker tout type d'objet, y compris String, Integer, etc., tandis que List <String> ne peut être utilisé que pour stocker des chaînes.
 

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

9. Les génériques peuvent-ils être utilisés dans Array?
C'est probablement la plus simple des questions d'entretien sur les génériques Java, bien sûr, le principe est que vous devez savoir que Array ne prend pas en charge les génériques en fait , c'est pourquoi Joshua Bloch dans le livre Effective Java Il est recommandé d'utiliser List au lieu de Array, car List peut fournir des garanties de sécurité de type au moment de la compilation, mais Array ne le peut pas.

10. Comment éviter les avertissements non vérifiés en Java?
Si vous mélangez des types génériques et primitifs, comme le code suivant, le compilateur javac de Java 5 générera des avertissements non vérifiés, tels que List <String> rawList = new ArrayList ()
Remarque: Hello.java utilise des opérations non cochées ou non sécurisées,
ces avertissements peuvent être protégés par l'annotation @SuppressWarnings ("non cochée").

11. Quelle est la différence entre List <Object> et le type primitif List en Java?
La principale différence entre le type primitif et le type de paramètre <Object> est que le compilateur n'effectuera pas de contrôles de sécurité de type sur les types primitifs au moment de la compilation, mais Vérifiez le type avec les paramètres . En utilisant Object comme type, vous pouvez indiquer au compilateur que la méthode peut accepter n'importe quel type d'objet, tel que String ou Integer. L'objectif de cette question réside dans la compréhension correcte des types primitifs dans les génériques. La deuxième différence entre eux est que vous pouvez passer n'importe quel type générique avec des paramètres à la méthode qui accepte le type d'origine List, mais vous ne pouvez pas passer List <String> à la méthode qui accepte List <Object>, car elle produira Erreur de compilation.
12. Quelle est la différence entre List <?> Et List <Object> en Java? Cette
question ressemble beaucoup à la question précédente, mais est complètement différente par essence. List <?> Est une List de type inconnu, et List <Object> est en fait une List de n'importe quel type. Vous pouvez affecter List <String>, List <Integer> à List <?>, Mais vous ne pouvez pas affecter List <String> à 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 différence entre List <String> et le type primitif List.
Cette question est similaire à "Quelle est la différence entre le type primitif et le type de paramètre". Le type de paramètre est de type sécurisé et sa sécurité de type est garantie par le compilateur , mais le type primitif List n'est pas de type sécurisé. Vous ne pouvez pas stocker n'importe quel type d'objet autre que String dans la liste des types de chaîne, mais vous pouvez stocker n'importe quel type d'objet dans la liste d'origine. Vous n'avez pas besoin d'utiliser un type générique avec des paramètres pour la conversion de type, mais pour les types primitifs, vous devez effectuer une conversion de type explicite.

 

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); //不需要显式的类型转换 - 编译器自动转换  

 

Ce billet de blog a été compilé par Xiaobai sur les épaules du Grand Dieu. Je remercie sincèrement les grands dieux pour leur dévouement désintéressé et leur esprit généreux.Cette qualité nous a beaucoup aidés à la croissance de Xiaobai dans la technologie.
Référence:
https://blog.csdn.net/seu_calvin/article/details/52230032
https://blog.csdn.net/sunxianghuang/article/details/51982979
—————————————— -
Avis de non - responsabilité: cet article est l'article original du blogueur CSDN "dix haricots trois exposition", et suivez l'accord de copyright CC 4.0 BY-SA, reproduit, veuillez joindre le lien source original et cette déclaration.
Lien d'origine: https://blog.csdn.net/zz13995900221/article/details/79736057

Je suppose que tu aimes

Origine blog.csdn.net/qianzhitu/article/details/102995570
conseillé
Classement