1. Qu'est-ce qu'un générique exactement?
Avant de discuter de l'inférence du type, nous devons revoir ce qui est générique. Les génériques sont de nouvelles fonctionnalités de Java SE 1.5. L'essence des génériques est un type paramétré, c'est-à-dire que le type de données opéré est spécifié comme un paramètre. En termes simples, ce serait des "variables de type". Ce type de variable peut être utilisé dans la création de classes, d'interfaces et de méthodes. La façon la plus simple de comprendre les génériques Java est de le considérer comme une syntaxe pratique qui peut vous faire économiser des opérations sur casting de type Java:
List <PLET> BOX = NOUVEAU ARRAYLIST <MPLE> (); Box.Add (new Apple ()); Apple Apple = Box.get (0);
Le code ci-dessus lui-même a clairement exprimé: Box est une List avec des objets Apple. La méthode get renvoie une instance d'objet Apple, et ce processus ne nécessite pas de conversion de type. Il n'y a pas de génériques, le code ci-dessus doit être écrit comme ceci:
Apple Apple = (Apple) Box.get (0);
Bien sûr, les génériques ne sont pas aussi simples que ce que j'ai décrit ici, mais ce n'est pas le protagoniste de notre époque. Les étudiants qui ne comprennent pas très bien les génériques doivent compenser les leçons ~ Bien sûr, les meilleurs documents de référence sont toujours les documents officiels.
2. Problèmes causés par les génériques (avant Java 7)
Le plus grand avantage des génériques est qu'ils offrent la sécurité du programme et peuvent être compatibles en arrière. Cependant, il y a aussi des choses qui rendent les développeurs malheureux. Le type de génériques doit être écrit à chaque fois qu'ils définissent. Cette spécification d'affichage semble non seulement un peu verbeuse, mais surtout, de nombreux programmeurs ne connaissent pas les génériques, ils ne peuvent donc pas donner de paramètres de type correct dans de nombreux cas. Désormais, le compilateur dépeint automatiquement les types de paramètres de génériques, ce qui peut réduire cette situation et améliorer la lisibilité du code.
3. Améliorations de la dérivation de types des génériques dans Java 7
L'utilisation de types génériques dans les versions précédentes de Java 7 nécessite d'ajouter des types génériques des deux côtés lors de la déclaration et de l'attribution de valeurs. Par exemple:
Map <string, entier> map = new hashmap <String, Integer> ();
Beaucoup de gens devaient être les mêmes que moi au début, et ils ont été perplexes par cela: je n'ai pas déclaré le type de paramètre dans la déclaration variable? Pourquoi ai-je encore besoin de l'écrire lorsque l'objet est initialisé? C'est aussi ce qui fait que les génériques se plaignent lorsqu'ils sont apparus pour la première fois. Cependant, il est gratifiant que pendant que Java s'améliore, les concepteurs améliorent également constamment le compilateur Java pour le rendre plus intelligent et humanisé. Voici notre protagoniste aujourd'hui: Type Pushdown ... eh bien ... ce n'est pas la dérivation de type, c'est-à-dire l'inférence de type. Lorsque ce type apparaît, lorsqu'il écrit le code ci-dessus, il peut omettre avec plaisir les types de paramètres lorsque l'instanciation de l'objet est instanciée, et cela devient comme ceci:
Map <string, entier> map = new hashmap <> ();
Dans cette instruction, le compilateur déduit automatiquement le type générique lors de l'instanciation HashMap basé sur le type générique lors de la déclaration variable. Encore une fois, assurez-vous de faire attention au "<>" derrière new HashMap . Ce n'est qu'en ajoutant que "<>" signifie qu'il s'agit d'une inférence de type automatique, sinon il s'agit d'un HashMap non générique, et une invite d'avertissement sera donnée lors de la compilation du code source à l'aide du compilateur. Cette paire de supports d'angle "<>" s'appelle "Diamond" dans le document officiel.
Cependant, la dérivation de type à l'heure actuelle n'est pas complète (même un produit semi-fini), car l'inférence du type lors de la création d'instances génériques dans Java SE 7 est limitée: uniquement si le type paramétré du constructeur est considérablement déclaré dans le contexte, l'inférence du type peut être utilisée, sinon elle ne fonctionnera pas. Par exemple: l'exemple suivant ne peut pas être compilé correctement dans Java 7 (mais il peut être compilé dans Java 8 maintenant, car le type générique est automatiquement déduit en fonction des paramètres de méthode):
List <string> list = new ArrayList <> (); list.add ("a"); // puisque addall prévoit d'obtenir des paramètres de la collection de types <? étend String>, l'instruction suivante ne peut pas transmettre list.addall (new ArrayList <> ());4. Réévolution en Java 8
Dans la dernière documentation officielle Java, nous pouvons voir la définition de la dérivation de type:
L'inférence de type est la capacité d'un compilateur Java à examiner chaque invocation de méthode et la déclaration correspondante pour déterminer l'argument (ou arguments) de type qui rend l'invocation applicable. L'algorithme d'inférence détermine les types d'arguments et, si disponible, le type que le résultat est attribué ou retourné. Enfin, l'algorithme d'inférence essaie de trouver le type le plus spécifique qui fonctionne avec tous les arguments.
En bref, la dérivation de type fait référence à la capacité du compilateur à déterminer les types de paramètres requis en fonction de la méthode que vous appelez et de la déclaration correspondante. Et un exemple est également donné dans la documentation officielle pour expliquer:
STATIC <T> T Pick (T A1, T A2) {return A2; } Serializable s = pick ("d", new ArrayList <string> ()); Ici, le compilateur peut déduire que le type du deuxième paramètre passé dans pick est Serializable .
Dans les versions Java précédentes, si l'exemple ci-dessus peut être compilé, vous devez écrire ceci:
Serializable s = this. <serializable> pick ("d", new ArrayList <string> ());La raison détaillée de la rédaction de ceci peut être vue dans le chapitre générique de la pensée de programmation Java de Bruce Eckel (quatrième édition). Bien sûr, ce livre est basé sur Java 6, et cette version n'a aucun concept de dérivation de type. Voyant cela, beaucoup de gens peuvent voir clairement le pouvoir de la dérivation de type dans la dernière version. Il ne se limite plus au processus de déclaration et d'instanciation des classes génériques, mais est étendu aux méthodes avec des paramètres génériques.
4.1 Type d'inférence et méthodes génériques
En ce qui concerne la dérivation de type et les méthodes génériques dans la nouvelle version, le document donne également un exemple légèrement plus complexe. Je l'ai posté ici. Le principe est le même que l'exemple Serializable ci-dessus, donc je n'entrerai pas dans les détails. Si vous souhaitez le consolider, vous pouvez jeter un coup d'œil:
classe publique BoxDemo {public static <u> void addbox (u u, java.util.list <box <u>> boxes) {box <u> box = new Box <> (); box.set (u); boxes.add (boîte); } public static <u> voidputboxes (java.util.list <box <u>> boxes) {int compter = 0; for (box <u> box: boxes) {u boxContents = box.get (); System.out.println ("Box #" + COMTER + "Contient [" + BoxContents.ToString () + "]"); compteur ++; }} public static void main (String [] args) {java.util.arraylist <box <Integer>> listoFintegerboxes = new java.util.arraylist <> (); BoxDemo. <Integer> AddBox (Integer.ValueOf (10), ListoFIntegerBoxes); BoxDemo.AddBox (Integer.ValueOf (20), ListoFIntegerBoxes); BoxDemo.AddBox (Integer.ValueOf (30), ListoFIntegerBoxes); BoxDemo.Outputboxes (ListoFIntegerBoxes); }}La sortie de code ci-dessus est:
La boîte # 0 contient [10] la boîte # 1 contient [20] la boîte # 2 contient [30]
Permettez-moi de mentionner que la mise au point de la méthode générique addBox est la description de type que vous n'avez plus besoin d'afficher dans l'appel de la méthode dans la nouvelle version java, comme celle-ci:
BoxDemo. <Integer> AddBox (Integer.ValueOf (10), ListoFIntegerBoxes);
Le compilateur peut en déduire automatiquement que le type de paramètre est Integer des paramètres transmis dans addBox .
4.2 Type d'inférence et constructeurs génériques des classes génériques et non génériques
Eh bien ... cela peut être une meilleure phrase en anglais: Type Inférence et constructeurs génériques des classes génériques et non génériques
En fait, les constructeurs génériques ne sont pas des brevets pour les classes génériques. Les classes non génériques peuvent également avoir leurs propres constructeurs génériques. Jetez un œil à cet exemple:
classe myClass <x> {<T> myClass (t t) {// ...}}Si l'instanciation suivante est faite à la classe MyClass:
New myClass <Integer> ("") Ok, nous montrons ici que le type de paramètre X de MyClass est Integer , et pour le constructeur, le compilateur déduit que le paramètre formel T est String basée sur l'objet String entrant (""). Cela a été implémenté dans la version Java7. Quelles améliorations ont été apportées dans Java8? Après Java8, nous pouvons écrire cette instanciation d'une classe générique avec un constructeur générique comme ceci:
MyClass <Integer> myObject = new myClass <> (""); Oui, il s'agit toujours de la paire de supports d'angle (<>), qui est appelé Diamond, afin que notre compilateur puisse déduire automatiquement que les paramètres formels x sont Integer et que T est String . Ceci est en fait très similaire à notre exemple initial de Map<String,String> , sauf qu'il existe une généricisation du constructeur.
Il convient de noter que la dérivation de type ne peut être dérivée qu'en fonction du type de paramètre de l'appel, du type de cible (cela sera discuté bientôt) et du type de retour (s'il y a un retour) et ne peut pas être dérivé en fonction de certaines exigences après le programme.
4.3 Type cible
Comme mentionné précédemment, le compilateur peut effectuer une dérivation de type basée sur le type cible. Le type de cible d'une expression se réfère au type de données correct dont le compilateur a besoin en fonction de l'emplacement de l'expression. Par exemple, cet exemple:
statique <T> list <T> videList (); list <string> listOne = collections.emptyList ();
Ici, la liste <string> est le type de cible, car ce qui est nécessaire ici est List<String> , et Collections.emptyList() renvoie List<T> , donc le compilateur ici infère que t doit être String . C'est OK dans Java 7 et 8. Cependant, dans Java 7, il ne peut pas être compilé normalement dans la situation suivante:
void processStringList (list <string> stringList) {// Process StringList} processStringList (CollectionS.EmptyList ());À l'heure actuelle, Java7 donnera ce message d'erreur:
// list <objet> ne peut pas être converti en liste <string>
Raison: Collections.emptyList() Renvoie List<T> , et t ici nécessite un type spécifique, mais parce qu'il ne peut pas être déduit de la déclaration de méthode que ce qui est requis est String , le compilateur donne à t une valeur Object . De toute évidence, List<Object> ne peut pas être convertie en List<String>. Donc, dans la version Java7, vous devez appeler cette méthode comme ceci:
processStringList (collections. <string> videList ());
Cependant, dans Java 8, en raison de l'introduction du concept de type cible, il est évident que ce dont le compilateur a besoin est List<String> (c'est-à-dire le type cible ici), donc le compilateur infère que le T dans la List<T> doit être String , donc la description de processStringList(Collections.emptyList()); est ok.
L'utilisation de types de cibles est la plus évidente dans les expressions de lambda.
Résumer
OK, ce qui précède est quelques idées personnelles sur la dérivation de type en Java. En résumé, la dérivation de type de plus en plus parfaite est de terminer un travail de conversion de type qui semble naturel, mais tout ces travaux sont laissés au compilateur pour la dérivation automatique plutôt que de permettre aux développeurs de l'afficher. J'espère que le contenu de cet article sera utile à tout le monde pour apprendre Java. Si vous avez des questions, vous pouvez laisser un message pour communiquer.