1. Proposer le concept de génériques (pourquoi les génériques sont-ils nécessaires)?
Tout d'abord, regardons le code court suivant:
public class GenerICTest {public static void main (String [] args) {list list = new ArrayList (); list.add ("qqyumidi"); list.add ("maïs"); list.add (100); for (int i = 0; i <list.size (); i ++) {String name = (string) list.get (i); // 1 system.out.println ("name:" + name); }}}Définissez une collection de type de liste, ajoutez d'abord deux valeurs de type de chaîne, puis ajoutez une valeur de type entier. Ceci est complètement autorisé car le type de liste par défaut est l'objet pour le moment. Dans la boucle suivante, il est facile d'avoir des erreurs similaires à // 1 parce que j'ai oublié d'ajouter des valeurs de type entier ou d'autres raisons de codage dans la liste auparavant. Étant donné que l'étape de compilation est normale, l'exception "java.lang.classCastException" apparaîtra au moment de l'exécution. Par conséquent, ces erreurs sont difficiles à détecter pendant le processus de codage.
Pendant le processus d'encodage comme ci-dessus, nous avons constaté qu'il y avait deux principaux problèmes:
1. Lorsque nous mettons un objet dans la collection, la collection ne se souviendra pas du type de cet objet. Lorsque cet objet est à nouveau retiré de la collection, le type de compilation de l'objet modifié devient le type d'objet, mais son type d'exécution est toujours son propre type.
2. Par conséquent, lors de la suppression de l'élément de collection à // 1, les types forcés artificiellement doivent être convertis en type cible spécifique, et il est facile de voir l'exception "java.lang.classcastException".
Il y a donc un moyen de faire en sorte que la collection se souvienne de divers types d'éléments dans la collection, et pour y parvenir tant qu'il n'y a pas de problème pendant la compilation, il n'y aura pas d'exception "java.lang.classcastException" pendant l'exécution? La réponse est d'utiliser des génériques.
2. Qu'est-ce qu'un générique?
Génériques, c'est-à-dire "type paramétré". En ce qui concerne les paramètres, la chose la plus familière est d'avoir des paramètres concrètes lors de la définition d'une méthode, puis de passer les paramètres réels lors de l'appel de cette méthode. Alors, comment comprenez-vous le type de paramétrage? Comme son nom l'indique, cela signifie paramétrer le type à partir du type spécifique d'origine, similaire aux paramètres variables de la méthode. À l'heure actuelle, le type est également défini comme une forme de paramètre (peut être appelé un paramètre formel de type), puis le type spécifique (type de paramètre réel) est passé lorsqu'il est utilisé / invoqué.
Cela semble un peu compliqué. Tout d'abord, jetons un coup d'œil à l'exemple ci-dessus en utilisant l'écriture générique.
classe publique GenerICTest {public static void main (String [] args) {/ * list list = new ArrayList (); list.add ("qqyumidi"); list.add ("maïs"); list.add (100); * / List <string> list = new ArrayList <string> (); list.add ("qqyumidi"); list.add ("maïs"); //list.add(100); // 1 invite une erreur de compilation pour (int i = 0; i <list.size (); i ++) {String name = list.get (i); // 2 system.out.println ("name:" + name); }}}Après avoir utilisé l'écriture générique, une erreur de compilation se produit lorsque vous souhaitez ajouter un objet de type entier à // 1. Grâce à la liste <string>, il est directement limité que seuls les éléments du type de chaîne peuvent être contenus dans la collection de liste, il n'est donc pas nécessaire de lancer le type à // 2, car pour le moment, la collection peut se souvenir des informations de type de l'élément, et le compilateur peut confirmer qu'il s'agit de type de chaîne.
En combinant la définition générique ci-dessus, nous savons que dans List <string>, String est un paramètre de type, c'est-à-dire que l'interface de liste correspondante doit contenir des paramètres formels de type. De plus, le résultat de retour de la méthode get () est directement ce type de paramètre formel (c'est-à-dire le paramètre de type entrant correspondant). Jetons un coup d'œil à la définition spécifique de l'interface de liste:
La liste d'interface publique <E> étend la collection <e> {int size (); booléen iSempty (); Boolean contient (objet O); Iterator <e> iterator (); Objet [] toArray (); <T> t [] toArray (t [] a); booléen add (e e); booléen retire (objet o); Boolean ContientAll (Collection <?> C); booléen addall (collection <? étend e> c); booléen addall (int index, collection <? étend e> c); Boolean Removeall (Collection <?> C); Boolean Retainall (Collection <?> C); void clear (); booléen égaux (objet o); int hashcode (); E get (int index); E set (int index, e élément); void add (int index, e élément); E supprime (INT INDEX); int indexof (objet o); int lastIndexof (objet o); ListIterator <e> listIterator (); ListeTiterator <E> ListIterator (int index); List <e> sublist (int FromIndex, int toindex);}Nous pouvons voir qu'après l'adoption de la définition générique dans l'interface de liste, E in <e> représente un paramètre formel de type, qui peut recevoir des paramètres de type spécifiques. Dans cette définition d'interface, où E apparaît, cela signifie que les mêmes paramètres de type acceptés de l'extérieur sont acceptés.
Naturellement, ArrayList est une classe d'implémentation pour l'interface de liste, et sa forme de définition est:
À partir de cela, nous comprenons du point de vue du code source pourquoi l'objet de type entier est mal compilé à // 1, et le type obtenu à // 2 est directement le type de chaîne.
classe publique ArrayList <E> étend AbstractList <e> implémente List <e>, RandomAccess, Clonable, java.io.Serializable {public boolean add (e e) {EnsurecapacityInternal (taille + 1); // incréments modCount !! elementData [size ++] = e; Retour Vrai; } public e get (int index) {rangeCheck (index); checkForComodification (); return arrayList.this.elementData (Offset + index); } //...Omit d'autres processus de définition spécifiques}3. Personnaliser les interfaces génériques, les classes génériques et les méthodes génériques
À partir du contenu ci-dessus, tout le monde a compris le processus de fonctionnement spécifique des génériques. Il est également connu que les interfaces, les classes et les méthodes peuvent également être définies à l'aide de génériques et utilisées en conséquence. Oui, lorsqu'il est utilisé spécifiquement, il peut être divisé en interfaces génériques, classes génériques et méthodes génériques.
Les interfaces génériques personnalisées, les classes génériques et les méthodes génériques sont similaires à List et ArrayList dans le code source Java ci-dessus. Comme suit, nous examinons la définition de classe générique et de méthode la plus simple:
classe publique GenerICTEST {public static void main (String [] args) {box <string> name = new Box <string> ("Corn"); System.out.println ("Name:" + name.getData ()); }} Class Box <T> {Données T privées; Public Box () {} Public Box (T data) {this.data = data; } public t getData () {return data; }}Dans le processus de définition des interfaces génériques, des classes génériques et des méthodes génériques, nos paramètres communs tels que T, E, K, V, etc. sont souvent utilisés pour représenter des paramètres formels génériques car ils reçoivent des paramètres de type provenant d'une utilisation externe. Donc, pour différents types de paramètres entrants, les types d'instances d'objet correspondantes sont-elles de la même manière?
classe publique GenerICTEST {public static void main (String [] args) {box <string> name = new Box <string> ("Corn"); Box <Integer> Age = new Box <Integer> (712); System.out.println ("Classe de noms:" + name.getClass ()); // com.qqyumidi.box System.out.println ("Classe d'âge:" + age.getClass ()); // com.qqyumidi.box system.out.println (name.getClass () == age.getClass ()); // vrai }}À partir de cela, nous avons constaté que lors de l'utilisation de classes génériques, bien que différents arguments génériques soient passés, différents types ne sont pas générés dans le vrai sens. Il n'y a qu'une seule classe générique qui passe dans différents arguments génériques en mémoire, c'est-à-dire qu'il s'agit toujours du type le plus basique d'origine (boîte dans cet exemple). Bien sûr, logiquement, nous pouvons le comprendre comme plusieurs types génériques différents.
La raison en est que l'objectif du concept de génériques en Java est qu'il ne fonctionne que dans l'étape de compilation de code. Pendant le processus de compilation, après avoir correctement vérifié les résultats génériques, les informations pertinentes des génériques seront effacées. C'est-à-dire que le fichier de classe compilé avec succès ne contient aucune information générique. Les informations génériques n'entreront pas dans la phase d'exécution.
Ceci est résumé en une phrase: les types génériques sont logiquement considérés comme plusieurs types différents et sont en fait les mêmes types de base.
Quatre. Type de joker
Après la conclusion ci-dessus, nous savons que Box <bumber> et Box <Integer> sont en fait les deux types de boîtes. Nous devons maintenant continuer à explorer une question. Ainsi, logiquement, la boîte <nom> et Box <Integer> peuvent-elles être considérées comme des types génériques avec des relations parent-enfant?
Pour clarifier ce problème, continuons à examiner l'exemple suivant:
classe publique GenerICTest {public static void main (String [] args) {box <nom> name = new Box <bumber> (99); Box <Integer> Age = new Box <Integer> (712); getData (nom); // La méthode getData (box <nombre>) dans le type générict est // non applicable pour les arguments (box <Integer>) getData (âge); // 1} public static void getData (box <nom> data) {System.out.println ("data:" + data.getData ()); }}Nous avons constaté qu'un message d'erreur est apparu à Code // 1: La méthode getData (Box <bumber>) dans le Generrictest T YPE n'est pas applicable pour les arguments (Box <Integer>). De toute évidence, en suscitant des informations, nous savons que Box <bumber> ne peut pas être logiquement considéré comme la classe parent de Box <Integer>. Alors, quelle est la raison?
public class GenerICTEST {public static void main (String [] args) {box <nteger> a = new Box <Integer> (712); Box <nombre> b = a; // 1 boîte <fload> f = new Box <fload> (3.14f); B.SetData (f); // 2} public static void getData (box <nombre> data) {System.out.println ("data:" + data.getData ()); }} Class Box <T> {Données T privées; Public Box () {} public Box (t data) {setData (data); } public t getData () {return data; } public void setData (t data) {this.data = data; }}Dans cet exemple, il y aura un message d'erreur à // 1 et // 2. Ici, nous pouvons utiliser la méthode contre-résistant pour l'expliquer.
En supposant que Box <bumber> peut être logiquement considéré comme la classe parent de Box <Integer>, il n'y aura pas d'invites d'erreur à // 1 et // 2. Ensuite, le problème se pose. De quel type est-il lors de la récupération des données via la méthode GetData ()? Entier? Flotter? ou numéro? De plus, en raison de l'ordre incontrôlable dans le processus de programmation, le jugement de type doit être fait lorsque cela est nécessaire et la conversion de type est effectuée. De toute évidence, cela contredit l'idée des génériques, donc logiquement, Box <bumber> ne peut pas être considéré comme la classe parent de Box <Integer>.
OK, regardons le premier exemple dans "Type Wildcards", nous connaissons la raison plus profonde de ses invites d'erreur spécifiques. Alors, comment le résoudre? Le quartier général peut définir une nouvelle fonction. Ceci est évidemment contraire au concept de polymorphisme en Java, nous avons donc besoin d'un type de référence qui peut être logiquement utilisé pour représenter la classe parentale de Box <Integer> et de Box <umber>, et donc, le type générique a vu le jour.
Les caractères génériques de type sont généralement utilisés à la place des arguments de type spécifique. Notez qu'il s'agit d'un paramètre de type, pas d'un paramètre de type! Et Box <?> Est logiquement la classe parent de toutes les boîtes <integer>, la boîte <nombres> ..., etc. Par conséquent, nous pouvons toujours définir des méthodes génériques pour répondre à ces exigences.
classe publique GenerICTEST {public static void main (String [] args) {box <string> name = new Box <string> ("Corn"); Box <Integer> Age = new Box <Integer> (712); Box <nombre> numéro = new Box <bor Swember> (314); getData (nom); getData (âge); getData (numéro); } public static void getData (box <?> data) {System.out.println ("data:" + data.getData ()); }}Parfois, nous pouvons également entendre parler des types supérieurs et inférieurs de caractères génériques. À quoi ressemble exactement?
Dans l'exemple ci-dessus, si vous avez besoin de définir une méthode qui fonctionne similaire à getData (), mais il y a d'autres restrictions sur les arguments de type: il ne peut s'agir que de la classe de nombres et de ses sous-classes. À l'heure actuelle, la limite supérieure des caractères génériques de type est nécessaire.
classe publique GenerICTEST {public static void main (String [] args) {box <string> name = new Box <string> ("Corn"); Box <Integer> Age = new Box <Integer> (712); Box <nombre> numéro = new Box <bor Swember> (314); getData (nom); getData (âge); getData (numéro); // getuppernumberData (nom); // 1 getuppernumberdata (âge); // 2 getUppernumberData (numéro); // 3} public static void getData (box <?> Data) {System.out.println ("data:" + data.getData ()); } public static void getuppernumberdata (box <? étend le nombre> data) {System.out.println ("data:" + data.getData ()); }}À ce stade, évidemment, l'appel à Code // 1 apparaîtra un message d'erreur, tandis que l'appel à // 2 // 3 sera normal.
La limite supérieure des caractères génériques de type est définie par la forme de la boîte <? étend le numéro>. En conséquence, la limite inférieure des caractères génériques de type est la forme de la boîte <? Super nombre>, et sa signification est exactement l'opposé de la limite supérieure des caractères génériques de type. Je ne l'expliquerai pas trop ici.
5. Chapitre supplémentaire
Les exemples de cet article sont principalement cités pour illustrer certaines idées dans les génériques et n'ont pas nécessairement une convivialité pratique. De plus, en ce qui concerne les génériques, je crois que ce que vous utilisez le plus est dans la collection. En fait, dans le processus de programmation réel, vous pouvez utiliser des génériques pour simplifier le développement et vous assurer bien la qualité du code. Et une chose à noter est qu'il n'y a pas de tableau soi-disant générique en Java.
Pour les génériques, la chose la plus importante est de comprendre les idées et les objectifs derrière eux.
Ce qui précède est une compilation de connaissances et d'informations sur les génériques Java. Nous continuerons d'ajouter des informations pertinentes à l'avenir. Merci pour votre soutien pour ce site Web!