IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage Java Discussion :

[Language][hashCode] Mes objets sont égaux mais n'ont pas le même hash


Sujet :

Langage Java

  1. #1
    Membre à l'essai
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    18
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 18
    Points : 14
    Points
    14
    Par défaut [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
    // 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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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

  2. #2
    Expert éminent sénior
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut 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++

  3. #3
    Membre à l'essai
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    18
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 18
    Points : 14
    Points
    14
    Par défaut 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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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;
    	}

  4. #4
    Membre à l'essai
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    18
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 18
    Points : 14
    Points
    14
    Par défaut
    élément de réponse:
    Ce n'est pas ma méthode equals qui est invoquée mais celle de la classe Object, et celle de la classe object fait référence aux adresses mémoires (r1==r2). Cela retourne évidemment false puisque j'ai fait 2 new. Mais alors quel intéret de faire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    static boolean eq(Object x, Object y) {
    return x == y || x.equals(y);
    }
    si x==y et x.equals(y) sont équivalents ?

    Je crois que c'est à cause de cette ligne que ce n'est pas la méthode que j'ai défini qui est invoquée :
    Object k = maskNull(key);
    key est un rapport, mais maskNull en fait un Object, ce qui fait que ce n'est pas la méthode equals de la classe rapport qui est invoquée mais celle de la classe Object.
    A partir de là, je ne voit pas vraiment comment faire... à part faire moi même une méthode qui néttoie les doublons uniquement à partir des attributs, mais j'aimerai bien éviter le bidouillage de ce genre.

    Merci beaucoup

  5. #5
    Expert éminent sénior
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut 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 :
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    	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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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).

  6. #6
    Membre à l'essai
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    18
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 18
    Points : 14
    Points
    14
    Par défaut
    Merci beaucoup,
    C'est bien ce que j'avais fini par conclure, (que c'était la méthode de la classe Object qui était appelée et pas la mienne).

    Merci également pour la précision sur les Set. Quand je pense au nombre de fois où je me suis compliqué juste à cause de cette faute de traduction...

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Mes photos sont supprimées, mais comment?
    Par omar24 dans le forum Windows Vista
    Réponses: 2
    Dernier message: 24/01/2011, 10h34
  2. Mes objets ne sont pas persistés
    Par JulienZ dans le forum JPA
    Réponses: 1
    Dernier message: 16/08/2009, 14h47
  3. mon programme refuse de faire comme il devrais quand mes objets sont dans des tableau
    Par alain57 dans le forum Interfaces Graphiques en Java
    Réponses: 6
    Dernier message: 08/02/2007, 08h29
  4. [Language]Serialization et objet immutable
    Par Repti dans le forum Langage
    Réponses: 7
    Dernier message: 26/12/2005, 18h29
  5. Pourquoi mes TPanels sont transparents?!!!!!
    Par pointer dans le forum Composants VCL
    Réponses: 9
    Dernier message: 08/11/2005, 12h09

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo