Quel est l'intérêt de l'opérateur diamant (<>) dans Java 7?

445

L'opérateur diamant dans java 7 autorise un code comme celui-ci:

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

Cependant en Java 5/6, je peux simplement écrire:

List<String> list = new LinkedList();

Ma compréhension de l'effacement de type est que ce sont exactement les mêmes. (Le générique est quand même supprimé au moment de l'exécution).

Pourquoi s'embêter avec le diamant? Quelle nouvelle fonctionnalité / sécurité de type permet-elle? Si cela ne donne aucune nouvelle fonctionnalité, pourquoi le mentionnent-ils comme une fonctionnalité? Ma compréhension de ce concept est-elle imparfaite?

tofarr
la source
4
Notez que ce n'est pas un problème si vous utilisez des méthodes de fabrique statiques, car Java saisit l'inférence sur les appels de méthode.
Brian Gordon
quand vous désactivez l'avertissement, c'est en fait inutile ... :) comme pour moi
Renetik
3
Il a été répondu, mais il est également dans le tutoriel Java (au milieu de la page): docs.oracle.com/javase/tutorial/java/generics/…
Andreas Tasoulas
Bel article à ce sujet sur dZone .
R. Oosterholt
2
À mon avis, il s'agit de sucre syntaxique pour List <String> list = new LinkedList <Quel que soit le type à gauche> (); c'est-à-dire le garder générique
Viktor Mellgren

Réponses:

496

Le problème avec

List<String> list = new LinkedList();

est que sur le côté gauche, vous utilisez le type générique tandis List<String>que sur le côté droit vous utilisez le type brutLinkedList . Les types bruts en Java n'existent effectivement que pour la compatibilité avec le code pré-générique et ne doivent jamais être utilisés dans du nouveau code, sauf si vous en avez absolument besoin.

Maintenant, si Java avait des génériques depuis le début et n'avait pas de types, tels que LinkedList, qui ont été créés à l'origine avant d'avoir des génériques, il aurait probablement pu faire en sorte que le constructeur d'un type générique infère automatiquement ses paramètres de type à partir de la gauche - côté main de la mission si possible. Mais il ne l'a pas fait, et il doit traiter différemment les types bruts et les types génériques pour une compatibilité ascendante. Cela les oblige à créer une manière légèrement différente , mais tout aussi pratique, de déclarer une nouvelle instance d'un objet générique sans avoir à répéter ses paramètres de type ... l'opérateur diamant.

En ce qui concerne votre exemple d'origine List<String> list = new LinkedList(), le compilateur génère un avertissement pour cette affectation car il le doit. Considère ceci:

List<String> strings = ... // some list that contains some strings

// Totally legal since you used the raw type and lost all type checking!
List<Integer> integers = new LinkedList(strings);

Les génériques existent pour fournir une protection au moment de la compilation contre les erreurs. Dans l'exemple ci-dessus, l'utilisation du type brut signifie que vous n'obtenez pas cette protection et obtiendrez une erreur au moment de l'exécution. C'est pourquoi vous ne devez pas utiliser de types bruts.

// Not legal since the right side is actually generic!
List<Integer> integers = new LinkedList<>(strings);

L'opérateur diamant, cependant, permet de définir le côté droit de l'affectation comme une véritable instance générique avec les mêmes paramètres de type que le côté gauche ... sans avoir à taper à nouveau ces paramètres. Il vous permet de conserver la sécurité des génériques avec presque le même effort que d'utiliser le type brut.

Je pense que la chose clé à comprendre est que les types bruts (sans <>) ne peuvent pas être traités de la même manière que les types génériques. Lorsque vous déclarez un type brut, vous n'obtenez aucun des avantages et la vérification de type des génériques. Vous devez également garder à l'esprit que les génériques sont une partie à usage général du langage Java ... ils ne s'appliquent pas seulement aux constructeurs sans argument de Collections!

ColinD
la source
31
La rétrocompatibilité est excellente, mais pas au détriment de la complexité, s'il vous plaît. Pourquoi Java 7 ne pourrait-il pas simplement introduire un -compatibilitycommutateur de compilation alors javacqu'en cas d'absence, il interdira tous les types bruts et appliquera uniquement les types strictement génériques? Cela rendra nos codes moins verbeux tels quels.
Rosdi Kasim
3
@Rosdi: D'accord, il n'y a pas besoin de types bruts dans le nouveau code. Cependant, je préfère fortement inclure le numéro de version Java dans le fichier source (au lieu de (mal) utiliser la ligne de commande), voir ma réponse.
maaartinus
37
Personnellement, je n'aime pas l'utilisation des diamants À MOINS QUE vous ne définissiez ET instanciez sur la même ligne. List<String> strings = new List<>()est OK, mais si vous définissez private List<String> my list;, puis à mi-chemin de la page avec laquelle vous instanciez my_list = new List<>(), ce n'est pas cool! Que contient à nouveau ma liste? Oh, laissez-moi chercher la définition. Tout à coup, l'avantage du raccourci diamant disparaît.
rmirabelle du
11
@rmirabelle En quoi est-ce différent de: my_list = getTheList() ? Il existe plusieurs méthodes pour résoudre ce type de problèmes: 1. utilisez un IDE qui vous montre les types de variables lors du survol de la souris. 2. utilisez des noms de variables plus significatifs, tels que private List<String> strings3. ne divisez pas la déclaration et l'initialisation des variables sauf si vous y êtes vraiment obligé.
Natix
1
@Morfidon: Oui, c'est toujours valable pour Java 8. Je suis sûr que vous devriez recevoir des avertissements.
ColinD
36

Votre compréhension est légèrement défectueuse. L'opérateur de diamant est une fonctionnalité intéressante car vous n'avez pas à vous répéter. Il est logique de définir le type une fois lorsque vous déclarez le type, mais cela n'a tout simplement pas de sens de le définir à nouveau sur le côté droit. Le principe DRY.

Maintenant, pour expliquer tout le fuzz sur la définition des types. Vous avez raison de dire que le type est supprimé lors de l'exécution, mais une fois que vous voulez récupérer quelque chose dans une liste avec la définition de type, vous le récupérez comme le type que vous avez défini lors de la déclaration de la liste, sinon il perdrait toutes les fonctionnalités spécifiques et n'aurait que le Fonctionnalités de l'objet, sauf lorsque vous convertissez l'objet récupéré en son type d'origine, ce qui peut parfois être très délicat et entraîner une exception ClassCastException.

L'utilisation List<String> list = new LinkedList()vous donnera des avertissements de type brut.

Octavian A. Damiean
la source
8
List<String> list = new LinkedList()est le bon code. Vous le savez et je le sais aussi. Et la question (si je comprends bien) est: pourquoi seul le compilateur java ne comprend pas que ce code est assez sécurisé?
Roman
22
@Roman: List<String> list = new LinkedList()n'est pas le bon code. Bien sûr, ce serait bien si c'était le cas! Et cela aurait probablement pu être le cas si Java avait des génériques depuis le début et n'avait pas à gérer la rétrocompatibilité des types génériques qui n'étaient pas génériques, mais c'est le cas.
ColinD
6
@ColinD Java n'a vraiment pas besoin de gérer la rétrocompatibilité dans chaque ligne . Dans tout fichier source Java utilisant des génériques, les anciens types non génériques doivent être interdits (vous pouvez toujours les utiliser <?>si vous vous connectez au code hérité) et l'opérateur diamant inutile ne doit pas exister.
maaartinus
16

Cette ligne provoque l'avertissement [non coché]:

List<String> list = new LinkedList();

Ainsi, la question se transforme: pourquoi l'avertissement [non vérifié] n'est pas supprimé automatiquement uniquement pour le cas où une nouvelle collection est créée?

Je pense que ce serait une tâche beaucoup plus difficile que d'ajouter une <>fonctionnalité.

UPD : Je pense aussi qu'il y aurait un gâchis s'il était légalement d'utiliser des types bruts «juste pour quelques petites choses».

romain
la source
13

En théorie, l'opérateur Diamond vous permet d'écrire du code plus compact (et lisible) en enregistrant des arguments de type répétés. En pratique, ce ne sont que deux caractères confus qui ne vous donnent rien. Pourquoi?

  1. Aucun programmeur sensé n'utilise de types bruts dans le nouveau code. Ainsi, le compilateur pourrait simplement supposer qu'en écrivant aucun argument de type, vous voulez qu'il les déduise.
  2. L'opérateur diamant ne fournit aucune information de type, il dit simplement au compilateur, "ça ira bien". Donc, en l'omettant, vous ne pouvez pas nuire. À tout endroit où l'opérateur diamant est légal, il pourrait être "déduit" par le compilateur.

À mon humble avis, avoir un moyen clair et simple de marquer une source comme Java 7 serait plus utile que d'inventer des choses aussi étranges. Dans un code aussi marqué, les types bruts pourraient être interdits sans rien perdre.

Btw., Je ne pense pas que cela devrait être fait en utilisant un commutateur de compilation. La version Java d'un fichier programme est un attribut du fichier, aucune option du tout. Utiliser quelque chose d'aussi trivial que

package 7 com.example;

pourrait être clair (vous préférerez peut-être quelque chose de plus sophistiqué, y compris un ou plusieurs mots clés de fantaisie). Cela permettrait même de compiler des sources écrites pour différentes versions de Java sans aucun problème. Cela permettrait d'introduire de nouveaux mots clés (par exemple, "module") ou de supprimer certaines fonctionnalités obsolètes (plusieurs classes non publiques non imbriquées dans un seul fichier ou quoi que ce soit) sans perdre la compatibilité.

maaartinus
la source
2
Avez-vous considéré la différence entre new ArrayList(anotherList)et new ArrayList<>(anotherList)(surtout si elle est attribuée à List<String>et anotherListest un List<Integer>)?
Paul Bellora
@ Paul Bellora: Non. Étonnamment pour moi, les deux compilent. Celui avec des diamants n'a même donné aucun avertissement. Cependant, je ne vois aucun sens à cela, pouvez-vous élaborer?
maaartinus
Désolé, je n'ai pas très bien expliqué. Voyez la différence entre ces deux exemples: ideone.com/uyHagh et ideone.com/ANkg3T Je souligne simplement qu'il est important d'utiliser l'opérateur Diamond au lieu d'un type brut, au moins lorsque des arguments avec des limites génériques sont passés in.
Paul Bellora
En fait, je n'avais pas pris le temps de lire la réponse de ColinD - il cite à peu près la même chose.
Paul Bellora
2
Donc, si nous sommes sur le point d'introduire une nouvelle syntaxe pour les types bruts, pour les quelques endroits où cela est vraiment nécessaire, pourquoi ne pas utiliser quelque chose comme new @RawType List(). C'est déjà une syntaxe Java 8 valide et les annotations de type permettent de l'utiliser à chaque endroit où cela est nécessaire, par exemple @RawType List = (@RawType List) genericMethod();. Considérant que les types bruts créent actuellement un avertissement du compilateur à moins qu'un approprié @SuppressWarningsn'ait été placé, @RawTypeserait un remplacement raisonnable et une syntaxe plus subtile n'est pas nécessaire.
Holger
8

Lorsque vous écrivez List<String> list = new LinkedList();, le compilateur génère un avertissement "non vérifié". Vous pouvez l'ignorer, mais si vous aviez l'habitude d'ignorer ces avertissements, vous risquez également de manquer un avertissement qui vous informe d'un problème de sécurité de type réel.

Il est donc préférable d'écrire un code qui ne génère pas d'avertissements supplémentaires, et l'opérateur Diamond vous permet de le faire de manière pratique sans répétition inutile.

axtavt
la source
4

Tout ce qui est dit dans les autres réponses est valable mais les cas d'utilisation ne sont pas complètement valides à mon humble avis. Si l'on vérifie Guava et surtout les trucs liés aux collections, la même chose a été faite avec des méthodes statiques. Par exemple Lists.newArrayList () qui vous permet d'écrire

List<String> names = Lists.newArrayList();

ou avec importation statique

import static com.google.common.collect.Lists.*;
...
List<String> names = newArrayList();
List<String> names = newArrayList("one", "two", "three");

La goyave a d'autres fonctionnalités très puissantes comme celle-ci et je ne peux vraiment pas penser à beaucoup d'utilisations pour le <>.

Il aurait été plus utile de choisir le comportement par défaut de l'opérateur diamant, c'est-à-dire que le type est déduit du côté gauche de l'expression ou si le type du côté gauche était déduit du côté droit. C'est ce qui se passe à Scala.

allprog
la source
3

Le point pour l'opérateur diamant est simplement de réduire le typage du code lors de la déclaration des types génériques. Cela n'a aucun effet sur l'exécution.

La seule différence si vous spécifiez en Java 5 et 6,

List<String> list = new ArrayList();

est que vous devez spécifier @SuppressWarnings("unchecked")le list(sinon vous obtiendrez un avertissement de cast non contrôlé). Je crois comprendre que l'exploitant de diamants essaie de faciliter le développement. Cela n'a rien à voir avec l'exécution des génériques à l'exécution.

Buhake Sindi
la source
vous n'avez même pas besoin d'utiliser cette annotation. Au moins dans Eclipse, vous pouvez simplement dire au compilateur de ne pas vous en avertir et vous allez bien ...
Xerus
Il vaut mieux avoir l'annotation. Tous les développeurs n'utilisent pas Eclipse ici.
Buhake Sindi