Quelles sont les raisons de la décision de ne pas avoir de méthode get entièrement générique dans l'interface de java.util.Map<K, V>
.
Pour clarifier la question, la signature de la méthode est
V get(Object key)
au lieu de
V get(K key)
et je me demande pourquoi (même chose pour remove, containsKey, containsValue
).
Réponses:
Comme mentionné par d'autres, la raison pour laquelle
get()
, etc. n'est pas générique car la clé de l'entrée que vous récupérez ne doit pas nécessairement être du même type que l'objet auquel vous passezget()
; la spécification de la méthode exige seulement qu'elles soient égales. Cela découle de la façon dont laequals()
méthode prend un objet en paramètre, et pas seulement du même type que l'objet.Bien qu'il puisse être généralement vrai que de nombreuses classes se sont
equals()
définies de sorte que ses objets ne peuvent être égaux qu'aux objets de sa propre classe, il existe de nombreux endroits en Java où ce n'est pas le cas. Par exemple, la spécification deList.equals()
indique que deux objets List sont égaux s'ils sont tous deux Lists et ont le même contenu, même s'il s'agit d'implémentations différentes deList
. Donc, pour revenir à l'exemple de cette question, selon la spécification de la méthode, il est possible d'avoir unMap<ArrayList, Something>
et pour moi d'appelerget()
avec unLinkedList
argument as, et il devrait récupérer la clé qui est une liste avec le même contenu. Cela ne serait pas possible s'ilget()
était générique et restreignait son type d'argument.la source
V Get(K k)
en C #?m.get(linkedList)
, pourquoi n'avez-vous pas définim
le type deMap<List,Something>
? Je ne peux pas penser à un cas d'utilisation où appelerm.get(HappensToBeEqual)
sans changer leMap
type pour obtenir une interface est logique.TreeMap
peut échouer lorsque vous passez des objets du mauvais type à laget
méthode mais peut passer occasionnellement, par exemple lorsque la carte est vide. Et pire encore, dans le cas d'un fourni,Comparator
lacompare
méthode (qui a une signature générique!) Pourrait être appelée avec des arguments du mauvais type sans aucun avertissement non contrôlé. Il s'agit d' un comportement brisé.Un formidable codeur Java chez Google, Kevin Bourrillion, a écrit à propos de ce problème dans un article de blog il y a quelque temps (certes dans le contexte de
Set
au lieu deMap
). La phrase la plus pertinente:Je ne suis pas entièrement sûr d'être d'accord avec cela en tant que principe - .NET semble bien exiger le bon type de clé, par exemple - mais cela vaut la peine de suivre le raisonnement dans le blog. (Après avoir mentionné .NET, il vaut la peine d'expliquer qu'une partie de la raison pour laquelle ce n'est pas un problème dans .NET est qu'il y a le plus gros problème dans .NET de variance plus limitée ...)
la source
Integer
et aDouble
ne peuvent jamais être égaux l'un à l'autre, c'est toujours une bonne question de se demander si aSet<? extends Number>
contient la valeurnew Integer(5)
.Set<? extends Foo>
. J'ai très fréquemment changé le type de clé d'une carte, puis j'ai été frustré que le compilateur ne puisse pas trouver tous les endroits où le code devait être mis à jour. Je ne suis vraiment pas convaincu que ce soit le bon compromis.Le contrat s'exprime ainsi:
(mon accent)
et en tant que tel, une recherche de clé réussie dépend de l'implémentation de la clé d'entrée de la méthode d'égalité. Cela ne dépend pas nécessairement de la classe de k.
la source
hashCode()
. Sans une implémentation correcte de hashCode (), une bonne implémentationequals()
est plutôt inutile dans ce cas.get()
n'a pas besoin de prendre un argument de typeObject
pour satisfaire le contact. Imaginez que la méthode get soit limitée au type de cléK
- le contrat serait toujours valide. Bien sûr, les utilisations où le type de temps de compilation n'était pas une sous-classe deK
ne pourraient plus être compilées, mais cela n'invalide pas le contrat, car les contrats discutent implicitement de ce qui se passe si le code compile.C'est une application de la loi de Postel, "soyez conservateur dans ce que vous faites, libéral dans ce que vous acceptez des autres".
Des contrôles d'égalité peuvent être effectués quel que soit le type; la
equals
méthode est définie sur laObject
classe et accepte anyObject
comme paramètre. Il est donc logique que l'équivalence des clés et les opérations basées sur l'équivalence des clés acceptent n'importe quelObject
type.Lorsqu'une carte renvoie des valeurs clés, elle conserve autant d'informations de type que possible, en utilisant le paramètre type.
la source
V Get(K k)
en C #?V Get(K k)
en C # parce que ça a aussi du sens. La différence entre les approches Java et .NET est vraiment seulement qui bloque les éléments non correspondants. En C # c'est le compilateur, en Java c'est la collection. Je suis furieux à propos des classes de collection incohérentes de .NET de temps en temps, maisGet()
et l'Remove()
acceptation d'un type correspondant vous empêche certainement de transmettre accidentellement une mauvaise valeur.contains : K -> boolean
.Je pense que cette section du didacticiel sur les génériques explique la situation (je souligne):
"Vous devez vous assurer que l'API générique n'est pas indûment restrictive; elle doit continuer à prendre en charge le contrat d'origine de l'API. Considérez à nouveau quelques exemples de java.util.Collection. L'API pré-générique ressemble à:
Une tentative naïve de le générer est:
Bien que ce type soit certainement sûr, il ne respecte pas le contrat d'origine de l'API. La méthode containsAll () fonctionne avec tout type de collection entrante. Il ne réussira que si la collection entrante ne contient vraiment que des instances de E, mais:
la source
containsAll( Collection< ? extends E > c )
alors?containsAll
uneCollection<S>
oùS
est un supertype deE
. Cela ne serait pas autorisé s'il l'étaitcontainsAll( Collection< ? extends E > c )
. En outre, comme cela est explicitement indiqué dans l'exemple, il est légitime de passer une collection d'un type différent (avec la valeur de retour étant alorsfalse
).La raison en est que le confinement est déterminé par
equals
ethashCode
quelles sont les méthodes activéesObject
et les deux prennent unObject
paramètre. Il s'agissait d'un premier défaut de conception dans les bibliothèques standard de Java. Couplé aux limitations du système de type Java, il oblige tout ce qui s'appuie sur equals et hashCode à prendreObject
.La seule façon d'avoir des tables de hachage typées et l' égalité en Java est à éviter
Object.equals
etObject.hashCode
et utiliser un substitut générique. Java fonctionnel est fourni avec des classes de type dans ce but:Hash<A>
etEqual<A>
. Un wrapper pourHashMap<K, V>
est fourni qui prendHash<K>
etEqual<K>
dans son constructeur. Ces classesget
etcontains
méthodes prennent donc un argument générique de typeK
.Exemple:
la source
Compatibilité.
Avant que les génériques ne soient disponibles, il y avait juste get (Object o).
S'ils avaient changé cette méthode pour obtenir (<K> o), cela aurait potentiellement forcé une maintenance massive du code sur les utilisateurs java juste pour que le code de travail soit à nouveau compilé.
Ils auraient pu introduire une méthode supplémentaire , par exemple get_checked (<K> o) et déconseiller l'ancienne méthode get () afin qu'il y ait un chemin de transition plus doux. Mais pour une raison quelconque, cela n'a pas été fait. (La situation dans laquelle nous nous trouvons actuellement est que vous devez installer des outils tels que findBugs pour vérifier la compatibilité des types entre l'argument get () et le type de clé déclaré <K> de la carte.)
Les arguments relatifs à la sémantique de .equals () sont faux, je pense. (Techniquement, ils sont corrects, mais je pense toujours qu'ils sont faux. Aucun concepteur sensé ne rendra jamais o1.equals (o2) vrai si o1 et o2 n'ont pas de superclasse commune.)
la source
Il y a une raison de plus, cela ne peut pas être fait techniquement, car cela casse la carte.
Java a une construction générique polymorphe comme
<? extends SomeClass>
. Une telle référence marquée peut pointer vers un type signé avec<AnySubclassOfSomeClass>
. Mais générique polymorphe rend cette référence en lecture seule . Le compilateur vous permet d'utiliser des types génériques uniquement comme type de méthode de retour (comme les simples getters), mais bloque l'utilisation de méthodes où le type générique est un argument (comme les setters ordinaires). Cela signifie que si vous écrivezMap<? extends KeyType, ValueType>
, le compilateur ne vous permet pas d'appeler la méthodeget(<? extends KeyType>)
et la carte sera inutile. La seule solution est de rendre cette méthode pas générique:get(Object)
.la source
Rétrocompatibilité, je suppose.
Map
(ouHashMap
) doit encore prendre en chargeget(Object)
.la source
put
(ce qui restreint les types génériques). Vous obtenez une compatibilité descendante en utilisant des types bruts. Les génériques sont «opt-in».Je regardais cela et je me demandais pourquoi ils l'avaient fait de cette façon. Je ne pense pas que l'une des réponses existantes explique pourquoi ils ne pouvaient pas simplement faire en sorte que la nouvelle interface générique n'accepte que le type approprié pour la clé. La vraie raison est que même s'ils ont introduit des génériques, ils n'ont PAS créé une nouvelle interface. L'interface de la carte est la même ancienne carte non générique qu'elle sert simplement de version générique et non générique. De cette façon, si vous avez une méthode qui accepte une carte non générique, vous pouvez la transmettre
Map<String, Customer>
et cela fonctionnerait toujours. Dans le même temps, le contrat pour get accepte Object, la nouvelle interface devrait donc également prendre en charge ce contrat.À mon avis, ils auraient dû ajouter une nouvelle interface et les implémenter à la fois sur la collection existante, mais ils ont décidé en faveur d'interfaces compatibles même si cela signifie une conception pire pour la méthode get. Notez que les collections elles-mêmes seraient compatibles avec les méthodes existantes, seules les interfaces ne le seraient pas.
la source
Nous effectuons un gros refactoring en ce moment et nous manquions ce get () fortement typé pour vérifier que nous n'avons pas manqué un get () avec l'ancien type.
Mais j'ai trouvé une astuce de contournement / laid pour la vérification du temps de compilation: créer une interface de carte avec get fortement tapé, containsKey, supprimer ... et le mettre dans le package java.util de votre projet.
Vous obtiendrez des erreurs de compilation juste en appelant get (), ... avec des types incorrects, tout le reste semble correct pour le compilateur (au moins à l'intérieur de l'éclipse kepler).
N'oubliez pas de supprimer cette interface après vérification de votre build car ce n'est pas ce que vous voulez en runtime.
la source