[Language][hashCode] Mes objets sont égaux mais n'ont pas le même hash
Bonjour,
J'utilise la classe HashSet pour stocker des objets de ma Classe Rapport.
La classe HashSet est censée avoir la particularité de ne pas pouvoir contenir 2 fois le même élément.
cette décision est basée sur le code suivant: extrait de la classe HashSet
Citation:
// Extrait de la classe HashMap
public Object put(Object key, Object value) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
for (Entry e = table[i]; e != null; e = e.next) {
if (e.hash == hash && eq(k, e.key)) {
Object oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, k, value, i);
return null;
}
static int hash(Object x) {
int h = x.hashCode();
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
static boolean eq(Object x, Object y) {
return x == y || x.equals(y);
}
Pourtant voici ce qui m'arrive, après avoir modifié la fonction HashCode de Rapport pour qu'elle se base sur les attributs et renvoie donc la même chose pour deux instances ayant les mêmes attributs.
Code:
1 2 3 4 5 6 7 8 9 10 11 12
|
//Je crée deux instances de Rapport avec les mêmes attributs.
Rapport r1 = new Rapport(...);
Rapport r2 = new Rapport(...);
HashSet s=new HashSet();
System.out.println(r1.hashCode()==r2.hashCode()); //retourne true
System.out.println(r1.equals(r2)); //retourne true
System.out.println(r2.equals(r1)); //retourne true
System.out.println(s.add(r1)); //retourne true
System.out.println(s.add(r2)); //retourne true
System.out.println(s.size()); //retourne 2 |
Ainsi mes 2élements étaient identiques, mais le 2 ème à tout de même été ajouté au HashSet s.
J'aimerai comprendre pourquoi, est-ce à cause de la partie de code que j'ai mise en bleu ? N'ai-je pas compris le fonctionnement de la méthode ?
Question subsidiaire, pourquoi ne peut-on pas faire un HashSet de HashSet ?
Merci beaucoup,
William Ledoux
Re: [hashCode] Mes objets sont égaux mais n'ont pas le même
Salut,
Citation:
Envoyé par BiLLKiLL
J'aimerai comprendre pourquoi, est-ce à cause de la partie de code que j'ai mise en bleu ? N'ai-je pas compris le fonctionnement de la méthode ?
Il faudrait voir ton implémentation des méthodes hashCode() et equals()...
Citation:
Envoyé par BiLLKiLL
Question subsidiaire, pourquoi ne peut-on pas faire un HashSet de HashSet ?
Je te retourne la question... Pourquoi affirmes tu que ce n'est pas possible (je ne vois pas le problème) ?
a++
Re: [hashCode] Mes objets sont égaux mais n'ont pas le même
Citation:
Envoyé par adiGuba
Citation:
Envoyé par BiLLKiLL
Question subsidiaire, pourquoi ne peut-on pas faire un HashSet de HashSet ?
Je te retourne la question... Pourquoi affirmes tu que ce n'est pas possible (je ne vois pas le problème) ?
HashCode implémente l'interface Set, or j'ai lu sur l'interface Set que :
"Un cas particulier des ensembles consiste à ne pas permettre à un objet Set de contenir un élément de type Set."
D'autre part, puisque je testes les hascode et qu'ils sont égaux, que je teste equals et que ça retourne true, je ne voit pas pourquoi cela dépend de mon implémentation, mais je veux bien montrer mon bidouillage :( .
Je précise que c'est juste à des fins de test, donc je ne me suis pas encore préocuppé des collisions que j'engendre.
Ma classe rapport est constituée d'un booléen (qui n'intervient pas ici) et d'un HashSet qui s'appelle ensembleErreur.
ma méthode hashCode consiste à prendre le HashCode de la chaine .toString de chacun des objets du HashSet et de faire un ou exclusif entre chaque :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public int hashCode()
{
int hash=-1;
Iterator i= ensembleErreur.iterator();
Object o;
while(i.hasNext())
{
o=i.next();
if(hash==-1)
hash=o.toString().hashCode();
else
hash=hash^o.toString().hashCode();
}
return hash;
} |
Ma méthode equals quant à elle vérifie que les HashSet des deux Rapports contiennent les mêmes éléments :
Code:
1 2 3 4 5 6 7 8
| public boolean equals(Rapport r)
{
if(r.ensembleErreur.containsAll(this.ensembleErreur)
&& this.ensembleErreur.containsAll(r.ensembleErreur))
return true;
else
return false;
} |
Re: [hashCode] Mes objets sont égaux mais n'ont pas le même
Citation:
Envoyé par BiLLKiLL
HashCode implémente l'interface Set, or j'ai lu sur l'interface Set que :
"Un cas particulier des ensembles consiste à ne pas permettre à un objet Set de contenir un élément de type Set."
Parce qu'il faut parfois se méfier des traductions... ;)
Je suppose en effet qu'il s'agit d'une erreur de traduction. Dans l'API de Set on peut lire :
Citation:
A special case of this prohibition is that it is not permissible for a set to contain itself as an element.
Donc un Set ne peut pas se contenir lui-même.
La raison est simple : la méthode hashCode() de Set indique que le hashcode d'un Set correspond à la somme de tous les éléments qu'il contient. Donc s'il se contient lui-même, la méthode provoquera une erreur de récursivité infini (hashCode s'appellant elle même à l'infini pour calculer son hashcode).
Par contre un Set peut très bien contenir d'autre Set, mais dans ce cas il faut bien sûr faire attention à ce que les Set qu'il contient ne soit pas modifié par la suite, sinon on peut se retrouver dans un état incohérent (a noter que cela peut également être vrai avec des objets "mutable"). Tout ceci est dans l'API de Set ;)
Citation:
Envoyé par BiLLKiLL
D'autre part, puisque je testes les hascode et qu'ils sont égaux, que je teste equals et que ça retourne true, je ne voit pas pourquoi cela dépend de mon implémentation, mais je veux bien montrer mon bidouillage :( .
Ca peut toujours être utile... En tout cas plus que de montrer du code de classes de l'API standard ;)
Pour en revenir à ton problème, il vient de la méthode equals() :
Code:
public boolean equals(Rapport r)
Il faut redéfinir les méthodes hashCode() et equals(Object) hérité de Object, car ce sont elles qui sont utilisé.
Or dans ton cas tu utilises un paramètre de type Rapport, donc ce n'est plus une redéfinition ("override" en anglais), mais une surcharge ("overload").
Dans le premier cas la méthode que tu définis remplace celle qui est hérité, et dans le second cas il s'agit une nouvelle méthode avec le même nom mais des paramètres différents. Donc dans ton cas tu te retrouve avec deux méthodes, et bien sûr tu n'appelles pas la même que l'implémentation du Set...
Pour t'en assurer tu peux écrire le code suivant :
Code:
1 2
| System.out.println(r1.equals(r2)); // Appel de la méthode equals(Rapport)
System.out.println(r1.equals((Object)r2)); // Appel de la méthode equals(Object) |
Il faut donc que tu changes ta méthode equals() en quelque chose qui ressemble à ceci :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public boolean equals(Object rapport) {
if (this==rapport) {
// Si la référence est égal c'est le même objet
return true;
}
if (rapport instanceof Rapport) {
Rapport r = (Rapport) rapport;
if(r.ensembleErreur.containsAll(this.ensembleErreur)
&& this.ensembleErreur.containsAll(r.ensembleErreur))
return true;
}
return false;
} |
a++
PS : si tu utilises Java 5.0, tu peux utiliser l'annotation @Override devant les méthodes lorsque tu veux les redéfinir. Le compilateur provoquera une erreur si ce n'est pas le cas (ie s'il ne s'agit pas d'une méthode hérité d'une classe parente).