2017-09-18 9 views
4

SituationEst-ce que hashCode() doit seulement utiliser le sous-ensemble des champs immuables de ceux utilisés dans equals()?

je devais remplacer equals() et comme il est recommandé aussi je réenregistrés la méthode hashCode() en utilisant les mêmes champs. Puis, quand je regardais un ensemble, qui ne contenait que l'un objet que je suis le résultat frustrant de

set.contains(object) 
=> false 

tout

set.stream().findFirst().get().equals(object) 
=> true 

Je comprends maintenant, que cela est dû à des changements qui ont été faites à object après il a été ajouté à set qui a de nouveau changé son hashCode. contains puis regarde la mauvaise clé et ne peut pas trouver le object.

Mes exigences pour la mise en œuvre sont

  • champs mutables sont nécessaires pour mettre en œuvre correctement l'utilisation equals()
  • ces objets en toute sécurité dans Collections hachage ou Maps telles cendres HashSet même si elles sont sujettes à des changements.

qui est en contradiction avec la convention que

Question

Y at-il des dangers à utiliser un sous-ensemble de champs qui sont utilisés dans equals() pour calculer hashCode() au lieu d'utiliser tous?

Plus précisément cela signifierait: equals() utilise un certain nombre de champs de l'objet alors que hashCode() utilise uniquement les champs qui sont utilisés dans equals() et qui sont immuables .

Je pense que cela devrait être correct, parce que

  • le contract est fullfilled: objets égaux produiront le même hashCode, tandis que le même hashCode ne necesairly signifie pas que les objets sont les mêmes.
  • Le hashCode d'un objet reste le même, même si un objet est exposé aux modifications et sera donc trouvé dans un HashSet avant et après ces modifications.

Related posts qui m'a aidé à comprendre mon problème, mais pas comment le résoudre: What issues should be considered when overriding equals and hashCode in Java? et Different fields for equals and hashcode

Répondre

0

Le contrat serait en effet REMPLIES.Le contrat impose que .equal() objets ont TOUJOURS le même .hashCode(). Le contraire ne doit pas être vrai et je me demande avec l'obsession de certaines personnes et IDE pour appliquer exactement cette pratique. Si cela était possible pour toutes les combinaisons possibles, alors vous découvrirez la fonction de hachage parfaite.

BTW, IntelliJ offre un bon assistant lors de la génération de hashCode et équivaut en traitant ces deux méthodes séparément et en permettant de différencier votre sélection. Évidemment, le contraire, alias offrant plus de champs dans les champs hashCode() et moins dans le equals() violerait le contrat.

0

Pour HashSet et les collections/cartes similaires, il est une solution valide d'avoir hashCode() utiliser seulement un sous-ensemble des champs de la méthode equals(). Bien sûr, vous devez réfléchir à l'utilité du code de hachage pour réduire les collisions sur la carte. Mais sachez que le problème revient si vous souhaitez utiliser des collections ordonnées comme TreeSet. Ensuite, vous avez besoin d'un comparateur qui ne donne jamais de collisions (renvoie zéro) pour les objets "différents", ce qui signifie que l'ensemble ne peut contenir que l'un des éléments entrant en collision. Votre description equals() implique que plusieurs objets existeront qui ne diffèrent que dans les champs mutables, puis vous perdez:

  • Y compris les champs mutables dans la méthode compareTo() peut changer le signe de comparaison, de sorte que l'objet doit déplacer vers une branche différente dans l'arbre. L'exclusion des champs mutables dans la méthode compareTo() vous permet d'avoir un élément entrant en collision au maximum dans TreeSet.

Je vous recommande fortement de penser à nouveau à votre concept d'égalité d'objets et de mutabilité.

0

C'est parfaitement valable pour moi. Supposons que vous ayez un Person:

final int name; // used in hashcode 
int income; // name + income used in equals 

name décide où l'entrée sera (pensez HashMap) ou qui seau sera choisi.

Vous mettez un Person comme Key intérieur HashMap: selon hashcode il va dans une certaine seau, seconde par exemple. Vous mettez à jour le income et recherchez Person sur la carte. Selon hashcode il doit être dans le deuxième seau, mais selon equals il est pas là:

static class Person { 
    private final String name; 

    private int income; 

    public Person(String name) { 
     super(); 
     this.name = name; 
    } 

    public int getIncome() { 
     return income; 
    } 

    public void setIncome(int income) { 
     this.income = income; 
    } 

    public String getName() { 
     return name; 
    } 

    @Override 
    public int hashCode() { 
     return name.hashCode(); 
    } 

    @Override 
    public boolean equals(Object other) { 
     Person right = (Person) other; 

     return getIncome() == right.getIncome() && getName().equals(right.getName()); 
    } 

} 

Et un test:

HashSet<Person> set = new HashSet<>(); 
    Person bob = new Person("bob"); 
    bob.setIncome(100); 
    set.add(bob); 

    Person sameBob = new Person("bob"); 
    sameBob.setIncome(200); 

    System.out.println(set.contains(sameBob)); // false 

Ce que vous manque, je pense est le fait que hashcode décide une seau où une entrée va (il pourrait y avoir beaucoup d'entrées dans ce seau) et c'est la première étape, mais equals décide si c'est bien, une entrée égale.

L'exemple que vous fournissez est parfaitement légal; mais celui que vous avez lié est l'inverse - il utilise plus champs en hashcode ce qui le rend donc incorrect.

Si vous comprenez ces détails qui premier hashcode est utilisé pour comprendre où et entrée pourrait résider et que plus tard tous (à partir du sous-ensemble ou un seau) sont essayé de trouver via equal - votre exemple serait sens.

0

Il est ok pour hashCode() d'utiliser un sous-ensemble des champs qui equals() utilisations, bien qu'il puisse éventuellement vous donner une légère baisse de performance.

Votre problème semble provenir de la modification de l'objet, alors qu'il se trouve encore à l'intérieur de l'appareil, d'une manière qui modifie le fonctionnement de hashCode() et/ou equals(). Chaque fois que vous ajoutez un objet à un HashSet (ou en tant que clé dans un HashMap), vous ne devez pas par la suite modifier les champs de cet objet qui sont utilisés par equals() et/ou hashCode(). Idéalement, tous les champs utilisés par equals() doivent être final. Si elles ne peuvent pas l'être, vous devez les traiter comme si elles étaient finales alors que l'objet est dans l'ensemble.

La même chose vaut pour TreeSet/TreeMap, mais s'applique aux champs utilisés par compareTo().

Si vous avez vraiment besoin de modifier les champs qui sont utilisés par equals() (ou par compareTo() dans le cas d'un TreeSet/TreeMap), vous devez:

  1. D'abord, enlever cet objet de l'ensemble;
  2. Modifiez ensuite l'objet;
  3. Et enfin l'ajouter à l'ensemble.