web-dev-qa-db-fra.com

Apache Commons est égal au constructeur / hashCode

Je suis curieux de savoir ce que les gens ici pensent de l'utilisation de org.Apache.commons.lang.builderEqualsBuilder/HashCodeBuilder pour implémenter le equals/hashCode? Serait-ce une meilleure pratique que d'écrire la vôtre? Est-ce que ça fonctionne bien avec Hibernate? Qu'en penses-tu?

154
aug70co

Les constructeurs de commons/lang sont excellents et je les utilise depuis des années sans perte de performances notable (avec et sans veille prolongée). Mais comme l'écrit Alain, la méthode Guava est encore plus agréable:

Voici un exemple de haricot:

public class Bean{

    private String name;
    private int length;
    private List<Bean> children;

}

Voici equals () et hashCode () mis en œuvre avec Commons/Lang:

@Override
public int hashCode(){
    return new HashCodeBuilder()
        .append(name)
        .append(length)
        .append(children)
        .toHashCode();
}

@Override
public boolean equals(final Object obj){
    if(obj instanceof Bean){
        final Bean other = (Bean) obj;
        return new EqualsBuilder()
            .append(name, other.name)
            .append(length, other.length)
            .append(children, other.children)
            .isEquals();
    } else{
        return false;
    }
}

et ici avec Java 7 ou supérieur (inspiré de Guava):

@Override
public int hashCode(){
    return Objects.hash(name, length, children);
}

@Override
public boolean equals(final Object obj){
    if(obj instanceof Bean){
        final Bean other = (Bean) obj;
        return Objects.equals(name, other.name)
            && length == other.length // special handling for primitives
            && Objects.equals(children, other.children);
    } else{
        return false;
    }
}

Remarque: ce code faisait à l'origine référence à Guava, mais comme l'ont souligné les commentaires, cette fonctionnalité a depuis été introduite dans le JDK. Par conséquent, Guava n'est plus nécessaire.

Comme vous pouvez le constater, la version de Guava/JDK est plus courte et évite les objets auxiliaires superflus. Dans le cas d’égal à égal, cela permet même de court-circuiter l’évaluation si un précédent appel Object.equals() renvoie false (pour être juste: commons/lang utilise une méthode ObjectUtils.equals(obj1, obj2) avec une sémantique identique qui pourrait être utilisé à la place de EqualsBuilder pour permettre le court-circuitage comme ci-dessus).

Donc: oui, les constructeurs communs de langage sont très préférables aux méthodes equals() et hashCode() construites manuellement (ou ces monstres terribles qu'Eclipse générera pour vous), mais le Java 7+/Guava sont encore meilleures.

Et une note sur Hibernate:

soyez prudent lorsque vous utilisez des collections paresseuses dans vos implémentations equals (), hashCode () et toString (). Cela échouera misérablement si vous n'avez pas de session ouverte.


Note (à propos d'égal à égal ()):

a) dans les deux versions de equals () ci-dessus, vous pouvez également utiliser l'un de ces raccourcis, ou les deux:

@Override
public boolean equals(final Object obj){
    if(obj == this) return true;  // test for reference equality
    if(obj == null) return false; // test for null
    // continue as above

b) En fonction de votre interprétation du contrat Equals (), vous pouvez également modifier la ou les lignes.

    if(obj instanceof Bean){

à

    // make sure you run a null check before this
    if(obj.getClass() == getClass()){ 

Si vous utilisez la deuxième version, vous voudrez probablement aussi appeler super(equals()) dans votre méthode equals(). Les avis diffèrent ici, le sujet est traité dans cette question:

bonne façon d'intégrer la super-classe dans une implémentation de Guava Objects.hashcode ()?

(bien qu'il s'agisse de hashCode(), il en va de même pour equals())


Note (inspiré par le commentaire de kayahr )

Objects.hashCode(..) (tout comme le Arrays.hashCode(...) sous-jacent) pourrait mal fonctionner si vous avez plusieurs champs primitifs. Dans de tels cas, EqualsBuilder peut en fait être la meilleure solution.

211
Sean Patrick Floyd

Les gens, réveillez-vous! Depuis Java 7 , il existe des méthodes d'assistance pour égal à et hashCode dans la bibliothèque standard, leur utilisation est totalement équivalente à celle des méthodes de Guava.

16

Si vous ne voulez pas dépendre d'une bibliothèque tierce (peut-être que vous utilisez un périphérique avec des ressources limitées) et que vous ne voulez même pas taper vos propres méthodes, vous pouvez également laisser le IDE = faire le travail, par exemple en utilisation Eclipse

Source -> Generate hashCode() and equals()...

Vous obtiendrez du code "natif" que vous pouvez configurer à votre guise et que vous devez obligatoirement supporter les modifications.


Exemple (Eclipse Juno):

import Java.util.Arrays;
import Java.util.List;

public class FooBar {

    public String string;
    public List<String> stringList;
    public String[] stringArray;

    /* (non-Javadoc)
     * @see Java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((string == null) ? 0 : string.hashCode());
        result = prime * result + Arrays.hashCode(stringArray);
        result = prime * result
                + ((stringList == null) ? 0 : stringList.hashCode());
        return result;
    }
    /* (non-Javadoc)
     * @see Java.lang.Object#equals(Java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        FooBar other = (FooBar) obj;
        if (string == null) {
            if (other.string != null)
                return false;
        } else if (!string.equals(other.string))
            return false;
        if (!Arrays.equals(stringArray, other.stringArray))
            return false;
        if (stringList == null) {
            if (other.stringList != null)
                return false;
        } else if (!stringList.equals(other.stringList))
            return false;
        return true;
    }

}
8
FrVaBe

EqualsBuilder et HashCodeBuilder ont deux aspects principaux qui diffèrent du code écrit manuellement:

  • traitement nul
  • création d'instance

EqualsBuilder et HashCodeBuilder facilitent la comparaison des champs pouvant être nuls. Avec un code écrit manuellement, cela crée beaucoup de passe-partout.

EqualsBuilder créera par contre une instance par appel de méthode égal. Si vos méthodes equals sont appelées souvent, cela créera beaucoup d'instances.

Pour Hibernate, les implémentations equals et hashCode ne font aucune différence. Ils sont juste un détail de mise en œuvre. Pour presque tous les objets de domaine chargés avec la mise en veille prolongée, la surcharge d'exécution (même sans analyse d'échappement) du générateur peut être ignorée. La surcharge de la base de données et de la communication sera importante.

Comme Skaffman l'a mentionné, la version de réflexion ne peut pas être utilisée dans le code de production. La réflexion sera lente et "l'implémentation" ne sera pas correcte pour toutes les classes sauf les plus simples. Prendre en compte tous les membres est également dangereux car les membres nouvellement introduits changent le comportement de la méthode equals. La version de réflexion peut être utile dans le code de test.

6
Thomas Jung

Si vous n’écrivez pas le vôtre, vous avez également la possibilité d’utiliser google guava (anciennement google collections)

4
Alain Pannetier

À mon avis, cela ne fonctionne pas bien avec Hibernate, en particulier les exemples de la réponse comparant la longueur, le nom et les enfants d'une entité. Hibernate conseille d'utiliser la clé commerciale à utiliser dans equals () et hashCode (), et ils ont leurs raisons. Si vous utilisez les générateurs auto equals () et hashCode () sur votre clé d'entreprise, les problèmes de performances doivent être pris en compte, comme mentionné précédemment. Mais les gens utilisent généralement toutes les propriétés ce qui est très mauvais à l’OMI. Par exemple, je travaille actuellement sur un projet dans lequel des entités sont écrites en utilisant Pojomatic avec @AutoProperty, ce que je considère comme un très mauvais modèle.

Leurs deux scénarios principaux pour utiliser hashCode () et equals () sont:

  • lorsque vous placez des instances de classes persistantes dans un ensemble (méthode recommandée pour représenter des associations à valeurs multiples) et
  • lorsque vous utilisez la reconnexion d'instances détachées

Supposons donc que notre entité ressemble à ceci:

class Entity {
  protected Long id;
  protected String someProp;
  public Entity(Long id, String someProp);
}

Entity entity1 = new Entity(1, "a");
Entity entity2 = new Entity(1, "b");

Les deux sont la même entité pour Hibernate, qui ont été extraites d'une session à un moment donné (leur id et leur classe/table sont égaux). Mais quand on implémente auto equals () un hashCode () sur tous les accessoires, qu'avons-nous?

  1. Lorsque vous placez l'entité2 dans l'ensemble persistant où l'entité1 existe déjà, cela sera mis deux fois et donnera lieu à une exception lors de la validation.
  2. Si vous souhaitez attacher l'entité détachée2 à la session, alors que l'entité1 existe déjà, elle ne sera probablement pas fusionnée correctement (je ne l'ai probablement pas testée en particulier).

Ainsi, pour 99% des projets que je réalise, nous utilisons les implémentations suivantes de equals () et hashCode () écrites une fois dans la classe d'entité de base, ce qui est cohérent avec les concepts d'Hibernate:

@Override
public boolean equals(Object obj) {
    if (StringUtils.isEmpty(id))
        return super.equals(obj);

    return getClass().isInstance(obj) && id.equals(((IDomain) obj).getId());
}

@Override
public int hashCode() {
    return StringUtils.isEmpty(id)
        ? super.hashCode()
        : String.format("%s/%s", getClass().getSimpleName(), getId()).hashCode();
}

Pour l’entité transitoire, je fais la même chose que Hibernate lors de l’étape de persistance, c’est-à-dire. J'utilise l'instance match. Pour les objets persistants, je compare la clé unique, qui est la table/id (je n’utilise jamais de clés composites).

0

Juste au cas où d’autres trouveraient cela utile, j’ai mis au point cette classe Helper pour le calcul de code de hachage qui évite la surcharge de création d’objet mentionnée ci-dessus (en fait, la surcharge de la méthode Objects.hash () est encore plus grande lorsque vous avez héritage car cela créera un nouveau tableau à chaque niveau!).

Exemple d'utilisation:

public int hashCode() {
    return HashCode.hash(HashCode.hash(timestampMillis), name, dateOfBirth); // timestampMillis is long
}

public int hashCode() {
    return HashCode.hash(super.hashCode(), occupation, children);
}

L'assistant HashCode:

public class HashCode {

    public static int hash(Object o1, Object o2) {
        return add(Objects.hashCode(o1), o2);
    }

    public static int hash(Object o1, Object o2, Object o3) {
        return hash(Objects.hashCode(o1), o2, o3);
    }

    ...

    public static int hash(Object o1, Object o2, ..., Object o10) {
        return hash(Objects.hashCode(o1), o2, o3, ..., o10);
    }

    public static int hash(int initial, Object o1, Object o2) {
        return add(add(initial, o1), o2);
    }

    ...

    public static int hash(int initial, Object o1, Object o2, ... Object o10) {
        return add(... add(add(add(initial, o1), o2), o3) ..., o10);
    }

    public static int hash(long value) {
        return (int) (value ^ (value >>> 32));
    }

    public static int hash(int initial, long value) {
        return add(initial, hash(value));
    }

    private static int add(int accumulator, Object o) {
        return 31 * accumulator + Objects.hashCode(o);
    }
}

J'ai pensé que 10 est le nombre maximum raisonnable de propriétés dans un modèle de domaine. Si vous en avez plus, pensez à refactoriser et à introduire plus de classe au lieu de conserver un tas de chaînes et de primitives.

Les inconvénients sont les suivants: ce n'est pas utile si vous avez principalement des primitives et/ou des tableaux que vous devez hacher en profondeur. (C'est normalement le cas lorsque vous devez gérer des objets plats (de transfert) hors de votre contrôle).

0
Vlad

Si vous ne faites que traiter avec le bean entity où id est une clé primaire, vous pouvez simplifier.

   @Override
   public boolean equals(Object other)
   {
      if (this == other) { return true; }
      if ((other == null) || (other.getClass() != this.getClass())) { return false; }

      EntityBean castOther = (EntityBean) other;
      return new EqualsBuilder().append(this.getId(), castOther.getId()).isEquals();
   }
0
DEREK LEE