Question

Cette question vous est proposée par le comportement étrange de HashMap.put ()

Je pense que je comprends pourquoi Map<K,V>.put prend un K mais Map<K,V>.get prend un Object, il semble que ne pas le faire va casser trop de code existant.

Nous entrons maintenant dans un scénario très sujet aux erreurs:

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>();
m.put(5L,"Five"); // compiler barfs on m.put(5, "Five")
m.contains(5); // no complains from compiler, but returns false

Cela n'aurait-il pas pu être résolu en renvoyant la valeur true si la Long valeur était définie sur une plage int et que les valeurs étaient égales?

Était-ce utile?

La solution

Voici la source de Long.java

public boolean equals(Object obj) {
    if (obj instanceof Long) {
        return value == ((Long)obj).longValue();
    }
    return false;
}

I.e. il faut que ce soit un type Long pour être égal. Je pense que la principale différence entre:

long l = 42L
int i = 42;
l == i

et votre exemple ci-dessus montre qu'avec les primitives, un élargissement implicite de la valeur int peut se produire. Cependant, avec les types d'objet, il n'existe pas de règles permettant la conversion implicite d'Integer en Long.

Découvrez également Java Puzzlers , il contient de nombreux exemples similaires.

Autres conseils

De manière générale, bien que cela ne soit pas strictement exprimé dans le contrat pour equals () , les objets ne doivent pas se considérer comme égaux à un autre objet qui n’appartient pas à la même classe (même s’il s’agit d’une sous-classe). Considérez la propriété symétrique - si a.equals (b) est true, alors b.equals (a) doit également être true.

Ayons deux objets, foo de classe Super et bar de classe Sub, qui s'étend equals(). Examinons maintenant l'implémentation de foo.equals(bar) dans Super, en particulier lorsqu'il est appelé Object. Foo sait seulement que bar est fortement typé comme HashSet<Sub>, donc pour obtenir une comparaison précise, il faut vérifier que c'est une instance de Super et si ce n'est pas retourner false. C'est, alors cette partie va bien. Il compare maintenant tous les champs d'instance, etc. (ou quelle que soit l'implémentation de la comparaison réelle), et les trouve égaux. Jusqu'ici, tout va bien.

Cependant, par contrat, il ne peut retourner vrai que s’il sait que bar.equals (foo) le sera également. Puisque bar peut être n'importe quelle sous-classe de Super, il n'est pas clair si la méthode equals () sera surpassée (et le sera probablement). Ainsi, pour être sûr que votre implémentation est correcte, vous devez l’écrire de manière symétrique et vous assurer que les deux objets appartiennent à la même classe.

Plus fondamentalement, les objets de classes différentes ne peuvent pas vraiment être considérés comme égaux - dans ce cas, un seul d'entre eux peut être inséré dans un <=>, par exemple.

Oui, mais tout dépend de l'algorithme de comparaison et de la distance à parcourir pour prendre les conversions. Par exemple, que voulez-vous qu'il se passe lorsque vous essayez m.Contains("5")? Ou si vous passez un tableau avec 5 comme premier élément? En termes simples, il semble être câblé & "; Si les types sont différents, les clés sont différentes &";.

.

Ensuite, prenez une collection avec object comme clé. Que voulez-vous qu'il se passe si vous put un 5L, puis essayez d'obtenir 5, "5", ...? Que se passe-t-il si vous 5F un int et un long et un short et que vous souhaitez rechercher un float?

S'agissant d'une collection générique (ou basée sur un modèle, ou quoi que vous souhaitiez l'appeler), il serait nécessaire de vérifier et de faire une comparaison spéciale pour certains types de valeur. Si K est double, vérifiez si l'objet transmis est <=>, <=>, <=>, <=>, ..., puis convertissez et comparez. Si K est <=> alors vérifiez si l'objet transmis est ...

Vous avez compris le point.

Une autre implémentation aurait pu être de lever une exception si les types ne correspondaient pas, cependant, et je le souhaiterais souvent.

Votre question semble raisonnable, mais ce serait une violation des conventions générales pour equals (), sinon son contrat, que cela soit vrai pour deux types différents.

Une partie de la conception du langage Java prévoyait que les objets ne soient jamais convertis implicitement en d'autres types, contrairement à C ++. Cela faisait partie de faire de Java un petit langage simple. Une partie raisonnable de la complexité de C ++ provient de conversions implicites et de leurs interactions avec d'autres fonctionnalités.

De plus, Java présente une dichotomie nette et visible entre les primitives et les objets. C'est différent des autres langues où cette différence est cachée sous les couvertures à titre d'optimisation. Cela signifie que vous ne pouvez pas vous attendre à ce que Long and Integer agisse comme un long et int.

Le code de bibliothèque peut être écrit pour masquer ces différences, mais cela peut être dommageable en rendant l'environnement de programmation moins cohérent.

Votre code devrait donc être ....

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>();
m.put(5L, "Five"); // compiler barfs on m.put(5, "Five")
System.out.println(m.containsKey(5L)); // true

Vous oubliez que java utilise votre code automatiquement, le code ci-dessus serait donc équivalent à

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>();
m.put(new Long(5L), "Five"); // compiler barfs on m.put(5, "Five")
System.out.println(m.containsKey(new Long(5))); // true
System.out.println(m.containsKey(new Long(5L))); // true

Donc, une partie de votre problème est la sélection automatique. L’autre partie est que vous avez différents types, comme d’autres affiches l’ont indiqué.

Les autres réponses expliquent bien pourquoi cela échoue, mais aucune d’entre elles n’indique comment écrire du code moins sujet aux erreurs autour de ce problème. Ne pas avoir à se rappeler d’ajouter des transtypages (sans aide du compilateur), les primitives de suffixe avec L, etc., n’est tout simplement pas acceptable à mon humble avis.

Je recommande fortement d'utiliser la bibliothèque de collections GNU Trove lorsque vous avez des primitives (et dans bien d'autres cas). Par exemple, il existe un TLongLongHashMap qui stocke les objets de manière inter-interne en tant que longs primitifs. En conséquence, vous ne finissez jamais avec la boxe / déballage, et ne finissez jamais avec des comportements inattendus:

TLongLongHashMap map = new TLongLongHashMap();
map.put(1L, 45L);
map.containsKey(1); // returns true, 1 gets promoted to long from int by compiler
int x = map.get(1); // Helpful compiler error. x is not a long
int x = (int)map.get(1); // OK. cast reassures compiler that you know
long x = map.get(1); // Better.

et ainsi de suite. Il n’est pas nécessaire d’obtenir le type correct, et le compilateur vous donne une erreur (que vous pouvez corriger ou écraser) si vous faites quelque chose d’idiot (essayez de stocker un long dans un int).

Les règles de diffusion automatique signifient que les comparaisons fonctionnent également correctement:

if(map.get(1) == 45) // 1 promoted to long, 45 promoted to long...all is well

En prime, la surcharge de mémoire et les performances d'exécution sont bien meilleures.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top