Quelles sont les raisons de la Carte.get(Object key) n'est pas (entièrement) générique
Quelles sont les raisons derrière la décision de ne pas disposer d'un générique de la méthode get
dans l'interface de java.util.Map<K, V>
.
De clarifier la question, la signature de la méthode est
V get(Object key)
au lieu de
V get(K key)
et je me demandais pourquoi (même chose pour remove, containsKey, containsValue
).
Même question concernant la Collecte: stackoverflow.com/questions/104799/...
Double Possible de Pourquoi ne pas Java Collections de supprimer des méthodes génériques?
Incroyable. Je suis à l'aide de Java depuis plus de 20 ans, et aujourd'hui je me rends compte de ce problème.
Double Possible de Pourquoi ne pas Java Collections de supprimer des méthodes génériques?
Incroyable. Je suis à l'aide de Java depuis plus de 20 ans, et aujourd'hui je me rends compte de ce problème.
OriginalL'auteur WMR | 2009-05-13
Vous devez vous connecter pour publier un commentaire.
Comme mentionné par d'autres, la raison pour laquelle
get()
, etc. n'est pas générique, car la clé de l'entrée de la récupération n'a pas à être le même type que l'objet que vous passez àget()
; la spécification de la méthode ne nécessite qu'ils soient égaux. Cela découle de la façon dont leequals()
méthode prend un Objet en paramètre, pas seulement le même type que l'objet.Bien qu'il peut être généralement vrai que de nombreuses classes ont
equals()
défini de telle sorte que ses objets ne peuvent être égaux aux objets de sa propre classe, il ya beaucoup d'endroits en Java si ce n'est pas le cas. Par exemple, le cahier des charges pourList.equals()
affirme que les deux objets de la Liste sont égales si elles sont à la fois des Listes et ont le même contenu, même si elles sont différentes implémentations deList
. Afin de revenir à l'exemple de cette question, conformément à la spécification de la méthode est possible d'avoir unMap<ArrayList, Something>
et pour moi d'appelerget()
avec unLinkedList
comme argument, et il doit récupérer la clé qui est une liste avec le même contenu. Ce ne serait pas possible siget()
sont génériques et restreint son type d'argument.V Get(K k)
en C#?La question est, si vous voulez l'appeler
m.get(linkedList)
, pourquoi n'avez-vous pas définirm
's typeMap<List,Something>
? Je ne peux pas penser à un cas d'utilisation où l'appelantm.get(HappensToBeEqual)
sans changer laMap
type pour obtenir une interface de sens.Wow, de graves défauts de conception. Vous n'obtenez pas de message d'avertissement du compilateur, vissé vers le haut. Je suis d'accord avec Elazar. Si c'est vraiment utile, ce dont je doute que cela arrive souvent, un getByEquals(Object key) semble plus raisonnable...
Cette décision semble comme il a été fait sur la base de la pureté théorique plutôt que pratique. Pour la majorité des usages, des développeurs préfèrent voir l'argument limitée par le type de modèle, que de l'avoir illimité à l'appui de bord des cas comme celui mentionné par newacct dans sa réponse. Laissant la non-basé sur un modèle de signatures crée plus de problèmes qu'elle n'en résout.
type de bien sûr” c'est une forte demande pour une construction qui peut échouer de façon imprévisible au moment de l'exécution. Ne pas affiner votre vue de hachage cartes qui arrive de travailler avec cela.
TreeMap
peut échouer lorsque vous transmettez des objets de type incorrect de laget
méthode, mais peut passer parfois, par exemple lorsque la carte se trouve être vide. Et pire encore, dans le cas d'une fourniComparator
lacompare
méthode (qui a un générique signature!) peut être appelée avec des arguments de type incorrect, sans décoché avertissement. Ce est comportement défectueux.OriginalL'auteur newacct
Un super codeur Java chez Google, Kevin Bourrillion, a écrit au sujet de exactement ce problème dans un post de blog il y a un moment (il est vrai que dans le contexte de
Set
au lieu deMap
). Les plus pertinents de la phrase:Je ne suis pas entièrement sûr je suis d'accord avec elle comme un principe .NET semble être bien exigeant le droit type de clé, par exemple - mais ça vaut la peine de suivre le raisonnement dans le billet de blog. (Avoir mentionné .NET, il vaut la peine d'expliquer qu'une partie de la raison pour laquelle il n'est pas un problème .NET est qu'il y a de la plus problème .NETTE de la plus limitée de la variance...)
Non, le post n'est pas faux. Même si un
Integer
et unDouble
ne peut jamais être égal à un autre, c'est toujours une question juste de se demander si unSet<? extends Number>
contient la valeurnew Integer(5)
.J'ai jamais voulu vérifier l'appartenance à un
Set<? extends Foo>
. J'ai très souvent changé le type de clé d'une carte et ensuite été déçus du fait que le compilateur n'a pas pu trouver tous les endroits où le code nécessaire mise à jour. Je ne suis pas vraiment convaincu que c'est le bon compromis.Il a toujours été cassé. C'est le point essentiel - le code est cassé, mais le compilateur ne peut pas l'attraper.
Et son toujours en panne, et juste nous a causé un bug ... génial réponse.
OriginalL'auteur Jon Skeet
Le contrat est exprimé ainsi:
(mon emphase)
et en tant que tel, la réussite d'une clé de recherche repose sur la touche input de la mise en œuvre de l'égalité de la méthode. Ce n'est pas nécessairement dépend de la classe de k.
hashCode()
. Sans une bonne mise en œuvre de hashCode(), joliment mis en œuvreequals()
est plutôt inutile dans ce cas.Je suppose, en principe, cela devrait vous permettre d'utiliser un léger proxy pour une clé, si recréer la clé a été difficile, aussi longtemps que equals() et hashCode() sont correctement mises en œuvre.
Pour autant que je suis au courant, seulement une table de hachage est tributaire de la code de hachage pour trouver le bon seau. Un TreeMap, par exemple, utilise un arbre de recherche binaire, et ne se soucie pas de hashCode().
Strictement parlant,
get()
n'a pas besoin de prendre un argument de typeObject
pour satisfaire le contact. Imaginez la méthode get limité à un type de cléK
- le contrat reste valide. Bien sûr, les utilisations où le temps de compilation type n'était pas une sous-classe deK
serait maintenant ne parviennent pas à compiler, mais ce n'est pas invalider le contrat, les contrats étant implicitement discuter de ce qui se passe si le code compile.OriginalL'auteur Brian Agnew
C'est une application de Postel de la Loi, "être prudent dans ce que vous faites, être libéral dans ce que vous acceptez des autres."
L'égalité des vérifications peuvent être effectuées quel que soit le type; le
equals
méthode est définie sur laObject
classe et accepte toutObject
en tant que paramètre. Ainsi, il est logique pour les principaux équivalence, et les opérations de base sur les principaux équivalence, à accepter toutObject
type.Lorsqu'une carte renvoie des valeurs de clé, il conserve autant de type d'informations qu'il peut, en utilisant le paramètre de type.
V Get(K k)
en C#?C'est
V Get(K k)
en C#, car elle aussi a du sens. La différence entre le Java et le .NET approches est vraiment seulement ce que les blocs de non-appariement des choses. En C# c'est le compilateur, en Java, c'est la collection. Je rage .NET incohérente de la collecte des cours une fois dans un tout, maisGet()
etRemove()
seulement d'accepter un appariement de type certainement vous évite de passer une mauvaise valeur.C'est une erreur de l'application de Postel est la Loi. Être libéral dans ce que vous acceptez que d'autres, mais pas trop libérale. Cet idiot API signifie que vous ne pouvez pas faire la différence entre "pas dans la collection" et "vous avez fait un typage statique erreur". Plusieurs milliers de perdu programmeur heures auraient pu être évités avec : K -> valeur booléenne.
Des cours qui ont été
contains : K -> boolean
.Postel était faux.
OriginalL'auteur erickson
Je pense que cette section de Génériques Tutoriel vous explique la situation (mon emphase):
"Vous avez besoin pour faire certain que l'API générique n'est pas trop restrictive; il doit
continuer à soutenir le contrat original de l'API. Considérons à nouveau quelques exemples
à partir de java.util.Collection. Le pré-générique de l'API ressemble:
Naïve tentative de generify c'est:
Tout cela est certainement le type de sécurité, il n'est pas à la hauteur de l'API du contrat initial.
Le containsAll() la méthode fonctionne avec n'importe quel type de l'arrivée de la collection. Il ne
réussir si la future collection vraiment ne contient que des instances de E, mais:
collection peut être différente, peut-être
parce que l'appelant ne connaît pas le
précise le type de la collection en cours
passé, ou peut-être parce que c'est un
La Collection de<S>,où S est un
sous-type de E.
légitime d'appeler containsAll() avec
une collection d'un type différent. L'
routine de travail, retour faux."
containsAll( Collection< ? extends E > c )
, alors?bien que pas donné comme un exemple ci-dessus, il est également nécessaire de permettre
containsAll
avec unCollection<S>
oùS
est un supertype deE
. Ce ne serait pas autorisée si elle étaitcontainsAll( Collection< ? extends E > c )
. En outre, comme est explicitement indiqué dans l'exemple, il est légitime de passer un collection d'un type différent (avec la valeur de retour étant alorsfalse
).Il ne devrait pas être nécessaire pour permettre containsAll avec une collection d'un supertype de E. je soutiens qu'il est nécessaire de refuser cet appel avec un type statique contrôle pour éviter un bug. C'est un drôle de contrat, je pense que c'est le point de la question d'origine.
OriginalL'auteur Yardena
La raison en est que le confinement est déterminé par
equals
ethashCode
qui sont des méthodes surObject
et les deux prendre unObject
paramètre. C'est un des premiers défauts de conception en Java librairies standard. Couplé avec des limitations en Java du type de système, il force tout ce qui s'appuie sur equals et hashCode de prendreObject
.La seule façon d'avoir de type-safe tables de hachage et de l'égalité en Java consiste à éviter
Object.equals
etObject.hashCode
et d'utiliser un substitut générique. Fonctionnel Java est livré avec des classes de type pour cet effet:Hash<A>
etÉgalité<A>
. Un wrapper pourHashMap<K, V>
est prévu que prendHash<K>
etEqual<K>
dans son constructeur. Cette classeget
etcontains
méthodes, par conséquent, prendre un argument générique de typeK
.Exemple:
Alors qu'il n'a pas l'éviter, je pense qu'il l'explique. Si ils ont changé la méthode equals pour forcer l'égalité des classes, ils ne pouvaient pas dire aux gens que le mécanisme sous-jacent de la localisation de l'objet dans la carte utilise equals() et hashmap() lorsque la méthode de prototypes pour ces méthodes ne sont pas compatibles.
OriginalL'auteur Apocalisp
Il y a une raison plus sérieuse, il ne peut pas être réalisé techniquement, parce qu'il brokes Carte.
Java a polymorphes générique de la construction comme
<? extends SomeClass>
. Marqué cette référence peut pointer vers type signé avec<AnySubclassOfSomeClass>
. Mais polymorphes générique fait référence readonly. Le compilateur permet l'utilisation de types génériques seulement comme type de retour de la méthode (comme simple getters), mais des blocs à l'aide de méthodes de type générique est argument (comme ordinaire définition).Cela signifie que si vous écrivez
Map<? 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 faire de cette méthode n'est pas générique:get(Object)
.si vous voulez dire "put": Le put (), méthode modifications de la carte et il ne sera pas disponible avec les génériques comme <? s'étend SomeClass>. Si vous l'appelez, vous avez compiler exception. Une telle carte sera "readonly"
OriginalL'auteur Owheee
De compatibilité.
Avant les génériques sont disponibles, il y avait juste get(Object o).
Avaient-ils changé cette méthode pour obtenir(<K> o) il aurait potentiellement forcé massive du code de la maintenance sur des utilisateurs java juste pour rendre le travail de code à compiler à nouveau.
Ils pourrait ont présenté un supplémentaires méthode, dire get_checked(<K> o) et de rendre caduque l'ancienne méthode get (), il y a donc plus doux chemin de transition. Mais pour une raison quelconque, cela n'a pas été fait. (La situation dans laquelle nous sommes, c'est maintenant que vous devez installer des outils comme findBugs pour vérifier le type de compatibilité entre le get() argument et la déclaration de type de clé <K> de la carte.)
Les arguments relatifs à la sémantique de .equals() sont faux, je pense. (Techniquement, ils sont corrects, mais je continue de penser qu'ils sont faux. Aucun concepteur dans son esprit droit ne va jamais faire de o1.equals(o2) vrai si o1 et o2 n'ont aucune commune de la super-classe.)
OriginalL'auteur Erwin Smout
Rétro-compatibilité, je suppose.
Map
(ouHashMap
) a encore besoin de soutienget(Object)
.put
(ce qui ne veut restreindre les types génériques). Vous obtenez la compatibilité descendante en utilisant des types. Les génériques sont des "opt-in".Personnellement, je pense que la raison la plus probable pour cette décision de conception est rétro-compatibilité.
OriginalL'auteur Anton Gogolev
Je la regardais et penser pourquoi ils ont fait ça de cette façon. Je ne pense pas que toute les réponses existantes explique pourquoi ils ne pouvaient pas juste faire de la nouvelle interface générique accepter uniquement le type de la clé. La vraie raison est que, même si ils ont introduit des génériques ils ne créent PAS une nouvelle interface. L'interface de la Carte est la même vieille non générique Carte, il sert juste à la fois génériques et non génériques. De cette façon, si vous avez une méthode qui accepte non générique Carte, vous pouvez passer à un
Map<String, Customer>
et il fonctionne encore. Dans le même temps, le contrat pour obtenir accepte Objet donc la nouvelle interface devrait soutenir ce contrat.À mon avis, ils doivent avoir ajouté une nouvelle interface et de mise en œuvre à la fois sur les collections existantes, mais ils ont décidé en faveur des interfaces compatibles, même si cela signifie le pire de la conception de la méthode get. Notez que les collections elles-mêmes être compatible avec les méthodes existantes, seules les interfaces ne serait pas.
OriginalL'auteur Stilgar
Nous faisons de gros refactoring tout à l'heure et il nous manquait ce fortement typé get() pour vérifier que nous n'avons pas manqué certains get() avec l'ancien type.
Mais j'ai trouvé une solution de contournement/laid truc pour le temps de compilation case: créer une Carte d'interface avec fortement typé obtenir, containsKey, supprimer... et de le mettre à java.util ensemble de votre projet.
Vous obtenez des erreurs de compilation juste pour l'appel de get(), ... avec les mauvais types, tout les autres semble ok pour le compilateur (au moins à l'intérieur eclipse kepler).
N'oubliez pas de supprimer cette interface après vérification de votre build que ce n'est pas ce que vous voulez dans l'exécution.
OriginalL'auteur henva