Entrer dans la zone grise du "sujet / hors sujet", mais nécessaire pour éliminer la confusion concernant la suggestion d'Oscar Reyes selon laquelle plus de collisions de hachage est une bonne chose car cela réduit le nombre d'éléments dans le HashMap. Je peux mal comprendre ce que dit Oscar, mais je ne semble pas être le seul: kdgregory, delfuego, Nash0, et je semble tous partager la même (mauvaise) compréhension.
Si je comprends ce qu'Oscar dit à propos de la même classe avec le même hashcode, il propose qu'une seule instance d'une classe avec un hashcode donné soit insérée dans le HashMap. Par exemple, si j'ai une instance de SomeClass avec un hashcode de 1 et une deuxième instance de SomeClass avec un hashcode de 1, une seule instance de SomeClass est insérée.
L'exemple de Java pastebin à http://pastebin.com/f20af40b9 semble indiquer que ce qui précède résume correctement ce que propose Oscar.
Indépendamment de toute compréhension ou malentendu, ce qui se passe, c'est que différentes instances de la même classe ne sont pas insérées une seule fois dans le HashMap si elles ont le même hashcode - pas tant qu'il n'a pas été déterminé si les clés sont égales ou non. Le contrat de hashcode exige que les objets égaux aient le même hashcode; cependant, il ne nécessite pas que les objets inégaux aient des codes de hachage différents (bien que cela puisse être souhaitable pour d'autres raisons) [1].
L'exemple pastebin.com/f20af40b9 (auquel Oscar fait référence au moins deux fois) suit, mais légèrement modifié pour utiliser des assertions JUnit plutôt que des lignes imprimées. Cet exemple est utilisé pour soutenir la proposition selon laquelle les mêmes codes de hachage provoquent des collisions et lorsque les classes sont les mêmes, une seule entrée est créée (par exemple, une seule chaîne dans ce cas spécifique):
@Test
public void shouldOverwriteWhenEqualAndHashcodeSame() {
String s = new String("ese");
String ese = new String("ese");
// same hash right?
assertEquals(s.hashCode(), ese.hashCode());
// same class
assertEquals(s.getClass(), ese.getClass());
// AND equal
assertTrue(s.equals(ese));
Map map = new HashMap();
map.put(s, 1);
map.put(ese, 2);
SomeClass some = new SomeClass();
// still same hash right?
assertEquals(s.hashCode(), ese.hashCode());
assertEquals(s.hashCode(), some.hashCode());
map.put(some, 3);
// what would we get?
assertEquals(2, map.size());
assertEquals(2, map.get("ese"));
assertEquals(3, map.get(some));
assertTrue(s.equals(ese) && s.equals("ese"));
}
class SomeClass {
public int hashCode() {
return 100727;
}
}
Cependant, le hashcode n'est pas l'histoire complète. Ce que l'exemple de pastebin néglige, c'est le fait que les deux s
et ese
sont égaux: ils sont tous les deux la chaîne "ese". Ainsi, insérer ou récupérer le contenu de la carte en utilisant s
ou ese
ou "ese"
comme clé sont tous équivalents cars.equals(ese) && s.equals("ese")
.
Un deuxième test démontre qu'il est erroné de conclure que des hashcodes identiques sur la même classe sont la raison pour laquelle la clé -> valeur s -> 1
est écrasée par ese -> 2
quand map.put(ese, 2)
est appelée dans le premier test. Dans le test deux, s
et ese
ont toujours le même hashcode (comme vérifié par assertEquals(s.hashCode(), ese.hashCode());
) ET ils sont la même classe. Cependant, s
et ce ese
sont des MyString
instances de ce test, pas des String
instances Java - la seule différence pertinente pour ce test étant les égaux: String s equals String ese
dans le test un ci-dessus, alors que MyStrings s does not equal MyString ese
dans le test deux:
@Test
public void shouldInsertWhenNotEqualAndHashcodeSame() {
MyString s = new MyString("ese");
MyString ese = new MyString("ese");
// same hash right?
assertEquals(s.hashCode(), ese.hashCode());
// same class
assertEquals(s.getClass(), ese.getClass());
// BUT not equal
assertFalse(s.equals(ese));
Map map = new HashMap();
map.put(s, 1);
map.put(ese, 2);
SomeClass some = new SomeClass();
// still same hash right?
assertEquals(s.hashCode(), ese.hashCode());
assertEquals(s.hashCode(), some.hashCode());
map.put(some, 3);
// what would we get?
assertEquals(3, map.size());
assertEquals(1, map.get(s));
assertEquals(2, map.get(ese));
assertEquals(3, map.get(some));
}
/**
* NOTE: equals is not overridden so the default implementation is used
* which means objects are only equal if they're the same instance, whereas
* the actual Java String class compares the value of its contents.
*/
class MyString {
String i;
MyString(String i) {
this.i = i;
}
@Override
public int hashCode() {
return 100727;
}
}
Sur la base d'un commentaire ultérieur, Oscar semble inverser ce qu'il a dit plus tôt et reconnaît l'importance des égaux. Cependant, il semble toujours que la notion d'égalité est ce qui compte, et non la «même classe», n'est pas claire (c'est moi qui souligne):
"Pas vraiment. La liste est créée uniquement si le hachage est le même, mais la clé est différente. Par exemple, si un String donne le hashcode 2345 et et et Integer donne le même hashcode 2345, alors l'entier est inséré dans la liste parce que String. equals (Integer) est false. Mais si vous avez la même classe (ou au moins .equals renvoie true), la même entrée est utilisée. Par exemple, new String ("one") et `new String (" one ") utilisé comme clés, utiliseront la même entrée. En fait, c'est le point TOUT de HashMap en premier lieu! Voyez par vous-même: pastebin.com/f20af40b9 - Oscar Reyes "
par rapport aux commentaires précédents qui abordent explicitement l'importance d'une classe identique et du même hashcode, sans mention d'égaux:
"@delfuego: voyez par vous-même: pastebin.com/f20af40b9 Donc, dans cette question, la même classe est utilisée (attendez une minute, la même classe est utilisée, non?) Ce qui implique que lorsque le même hachage est utilisé, la même entrée est utilisé et il n'y a pas de "liste" des entrées. - Oscar Reyes "
ou
"En fait, cela augmenterait les performances. Plus il y a de collisions eq, moins d'entrées dans l'équation de la table de hachage, moins de travail à faire. N'est-ce pas le hachage (qui a l'air bien) ni la table de hachage (qui fonctionne très bien) je parie que c'est sur l'objet création où la performance est dégradante. - Oscar Reyes "
ou
"@kdgregory: Oui, mais seulement si la collision se produit avec différentes classes, pour la même classe (ce qui est le cas) la même entrée est utilisée. - Oscar Reyes"
Encore une fois, je peux mal comprendre ce qu'Oscar essayait de dire. Cependant, ses commentaires originaux ont causé suffisamment de confusion pour qu'il semble prudent de tout éclaircir avec des tests explicites afin qu'il n'y ait pas de doutes persistants.
[1] - Tiré de Effective Java, deuxième édition par Joshua Bloch:
Chaque fois qu'elle est appelée sur le même objet plus d'une fois lors de l'exécution d'une application, la méthode hashCode doit systématiquement renvoyer le même entier, à condition qu'aucune information utilisée dans les comparaisons égales sur l'objet ne soit modifiée. Cet entier n'a pas besoin de rester cohérent d'une exécution d'une application à une autre exécution de la même application.
Si deux objets sont égaux selon la méthode equal s (Obj ect), alors l'appel de la méthode hashCode sur chacun des deux objets doit produire le même résultat entier.
Il n'est pas nécessaire que si deux objets sont inégaux selon la méthode égale s (Object), l'appel de la méthode hashCode sur chacun des deux objets doit produire des résultats entiers distincts. Cependant, le programmeur doit être conscient que la production de résultats entiers distincts pour des objets inégaux peut améliorer les performances des tables de hachage.