web-dev-qa-db-fra.com

Java - Méthode equals en classe de base et en sous-classes

J'ai une classe de base simple, qui est ensuite étendue par de nombreuses classes distinctes, qui introduisent potentiellement de nouveaux champs, mais pas nécessairement. J'ai défini une méthode equals dans la classe de base, mais j'ai également écrasé celle-ci pour quelques sous-classes. Est-il possible de mélanger les définitions dans la base/les sous-classes? Dans mon cas, c'était pour éviter la duplication de code en vérifiant les mêmes champs.

15
Bober02

Jetez un coup d'œil à "Implémentation d'equals () pour autoriser la comparaison de types mélangés" de Angelika Langer.

Voici une brève explication de certains problèmes et une solution possible:

Le contrat d'égalité dit (entre autres):

Il est symétrique: pour toute valeur de référence non nulle x et y, x.equals (y) doit renvoyer true si et seulement si y.equals (x) renvoie true.

Cela signifie que vous pourriez avoir des problèmes si votre sous-classe introduit de nouveaux champs et que vous comparez un objet de la classe de base (ou une autre sous-classe qui ne remplace pas égal à égal) à un objet de cette sous-classe.

NE PAS faire ce qui suit:

class BaseClass {
    private int field1 = 0;

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof BaseClass) {
            return field1 == ((BaseClass) obj).field1;
        }
        return false;
    }
}

class BadSubClass extends BaseClass {
    private int field2 = 0;

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof BadSubClass) {
            return super.equals(obj) 
                    && field2 == ((BadSubClass) obj).field2;
        }
        return false;
    }
}

parce que tu as

BaseClass baseClass = new BaseClass();
BadSubClass subClass = new BadSubClass();

System.out.println(baseClass.equals(subClass)); // prints 'true'
System.out.println(subClass.equals(baseClass)); // prints 'false'

Une solution possible:

Remplacez la vérification instanceof- par une comparaison de classe:

obj != null && obj.getClass() == getClass()

Avec cette solution, un objet de BaseClass ne sera jamais égal à un objet de toute sous-classe.

Si vous créez une autre variable SubClass sans le @Override de la méthode equals, deux objets SubClass- peuvent être égaux l'un de l'autre (si la vérification BaseClass.equals le permet), mais un objet SubClass- ne sera jamais égal à un objet BaseClass-variable .

Une bonne implémentation pourrait être la suivante:

class BaseClass {
    private int field1 = 0;

    @Override
    public boolean equals(Object obj) {
        if (obj != null && obj.getClass() == getClass()) {
            return field1 == ((BaseClass) obj).field1;
        }
        return false;
    }
}

class GoodSubClass extends BaseClass {
    private int field2 = 0;

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof GoodSubClass) {
            return super.equals(obj) && field2 == ((GoodSubClass) obj).field2;
        }
        return false;
    }
}

Veuillez vous référer à l'article mentionné ci-dessus pour des problèmes plus avancés et leurs solutions.

26
Raph

Non, il n'est pas possible de se conformer au contrat d'égalité lors de l'introduction de nouveaux champs pertinents pour la méthode de l'égalité. Voir "Effective Java" de Joshua Bloch pour plus d'informations.

Modifier:

Je n'ai pas le livre sous la main pour le moment, mais je pense que ce n'est pas grave si la classe de base est abstraite/ne peut pas être instanciée.

4
Puce

Je pense que c'est parfaitement bien tant que vous suivez equals () et hashcode () contract.

0
kosa

Bien que ce qui suit ne traite pas tous les cas, je l’ai trouvé assez pratique. J'ai utilisé cela plusieurs fois lorsque j'ai à la fois une SuperClass et une SubClass en jeu. Je ne veux pas les comparer, mais je ne veux pas non plus réimplémenter tous les SuperClass equals () pour SubClass. Il gère:

  • a.equals (b) == b.equals (a)
  • Ne duplique pas le code de comparaison de champ
  • Facilement généralisé pour toute profondeur de sous-classe
  • Subclass.equals (SuperClass) == false
  • Superclass.equals (SubClass) == false

Exemple de code

// implement both strict and asymmetric equality
class SuperClass {
   public int f1;
   public boolean looseEquals(Object o) {
      if (!(o instanceof SuperClass)) return false;
      SuperClass other = (SuperClass)o;
      return f1 == other.f1;
   }
   @Override public boolean equals(Object o) {
      return looseEquals(o) && this.getClass() == o.getClass();
   }
}
class SubClass extends SuperClass {
   public int f2;
   @Override public boolean looseEquals(Object o) {
      if (!super.looseEquals(o)) return false;
      if (!(o instanceof SubClass)) return false;
      SubClass other = (SubClass)o;
      return f2 == other.f2;
   }
   // no need to override equals()
}
0
Wheezil

Vous pouvez utiliser la méthode super() pour appeler la méthode de la classe que vous étendez afin d'éviter tout besoin de duplication de code.

public class BaseClass {
  public boolean equals(BaseClass other) {
    return (other.getBlahblah() == this.Blahblah && .....);
  }
}

public class DerivedClass extends BaseClass {
  public boolean equals(DerivedClass other) {
    return (super(other) && other.getNewAttribute() == this.NewAttribute.....);
  }
}
0
Grambot

Une approche tout à fait valable. Le problème se trouve dans l’une de vos sous-classes il must conserve la définition d’égaux tel qu’il est lié par son parent. Sinon, vous avez une fonction égal à cassé, ce qui peut entraîner des scénarios très uniques au cours de l'exécution.

0
Woot4Moo

J'imagine que c'est parfait pour fournir l'implémentation des méthodes equals(Object obj) et hashCode() dans superclasscomme Javal'a fait. Nous savons tous que Java fournit l'implémentation de la méthode hashCode() and equals(Object obj) dans la classe de base Java.lang.Object , et lorsque cela est nécessaire, nous leur donnons overridedans notre classname__. 

0
subodh