Existe-t-il une règle spécifique sur la manière dont Remplacer equals()
& hashCode()
dans sous-classes considérant super-champs ?? sachant qu'il y a beaucoup de paramètres: les super-champs sont privés/publics, avec/sans getter ...
Par exemple, Netbeans généré equals () et hashCode () ne prendront pas en compte les super-champs ... et
new HomoSapiens("M", "80", "1.80", "Cammeron", "VeryHot").equals(
new HomoSapiens("F", "50", "1.50", "Cammeron", "VeryHot"))
retournera vrai :(
public class Hominidae {
public String gender;
public String weight;
public String height;
public Hominidae(String gender, String weight, String height) {
this.gender = gender;
this.weight = weight;
this.height = height;
}
...
}
public class HomoSapiens extends Hominidae {
public String name;
public String faceBookNickname;
public HomoSapiens(String gender, String weight, String height,
String name, String facebookId) {
super(gender, weight, height);
this.name = name;
this.faceBookNickname = facebookId;
}
...
}
Si vous voulez voir les Netbeans générés, equals () & hashCode ():
public class Hominidae {
...
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Hominidae other = (Hominidae) obj;
if ((this.gender == null) ? (other.gender != null) : !this.gender.equals(other.gender)) {
return false;
}
if ((this.weight == null) ? (other.weight != null) : !this.weight.equals(other.weight)) {
return false;
}
if ((this.height == null) ? (other.height != null) : !this.height.equals(other.height)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 5;
hash = 37 * hash + (this.gender != null ? this.gender.hashCode() : 0);
hash = 37 * hash + (this.weight != null ? this.weight.hashCode() : 0);
hash = 37 * hash + (this.height != null ? this.height.hashCode() : 0);
return hash;
}
}
public class HomoSapiens extends Hominidae {
...
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final HomoSapiens other = (HomoSapiens) obj;
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
return false;
}
if ((this.faceBookNickname == null) ? (other.faceBookNickname != null) : !this.faceBookNickname.equals(other.faceBookNickname)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 7;
hash = 89 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 89 * hash + (this.faceBookNickname != null ? this.faceBookNickname.hashCode() : 0);
return hash;
}
}
Les enfants ne doivent pas interroger les membres privés de leurs parents
Mais évidemment , tous les champs significatifs doivent être pris en compte pour l’égalité et le hachage.
Heureusement, vous pouvez facilement satisfaire les deux règles.
En supposant que vous n'êtes pas bloqué à l'aide de l'égalité et du hashcode générés par NetBeans, vous pouvez modifier la méthode equals de Hominidae pour qu'elle utilise instance de comparaison plutôt que l'égalité de classe, puis l'utiliser directement. Quelque chose comme ça:
@Override
public boolean equals(Object obj) {
if (obj == null) { return false; }
if (getClass() != obj.getClass()) { return false; }
if (! super.equals(obj)) return false;
else {
// compare subclass fields
}
Bien sûr, le hashcode est simple:
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 89 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 89 * hash + (this.faceBookNickname != null ? this.faceBookNickname.hashCode() : 0);
return hash;
}
Sérieusement, que se passe-t-il avec NetBeans qui ne prend pas en compte les champs de la super-classe en appelant les méthodes de la super-classe?
Je préfère utiliser EqualsBuilder (et HashcodeBuilder) du paquet commons-lang pour faciliter la lecture de mes méthodes equals () et hashcode ().
Exemple:
public boolean equals(Object obj) {
if (obj == null) { return false; }
if (obj == this) { return true; }
if (obj.getClass() != getClass()) {
return false;
}
MyClass rhs = (MyClass) obj;
return new EqualsBuilder()
.appendSuper(super.equals(obj))
.append(field1, rhs.field1)
.append(field2, rhs.field2)
.append(field3, rhs.field3)
.isEquals();
}
De manière générale, il est difficile de garder symétrique et transitive l'implémentation d'égaux dans toutes les sous-classes.
Prenons une superclasse qui vérifie les champs x
et y
, et les contrôles de sous-classes pour x
, y
et z
.
Donc, une sous-classe == superclasse == sous-classe où z est différent entre la première instance de la sous-classe et la seconde, violant ainsi la partie transitive du contrat.
C'est pourquoi l'implémentation typique d'égaux recherchera getClass() != obj.getClass()
au lieu de faire une instance de. Dans l'exemple ci-dessus, si SubClass ou Superclass effectue une vérification, cela romprait la symétrie.
Le résultat est qu’une sous-classe peut certainement prendre en compte super.equals () mais doit également effectuer sa propre vérification getClass () pour éviter les problèmes ci-dessus, puis rechercher des valeurs égales sur ses propres champs. Ce serait un canard étrange d’une classe qui a changé son propre comportement d’égaux en fonction de champs spécifiques de la superclasse plutôt que de simplement si la superclasse renvoie des égaux.
Les règles sont:
de Object.equals () .
Alors, utilisez les champs nécessaires pour respecter les règles.
Étant donné que l'héritage rompt l'encapsulation, les sous-classes implémentant equals () et hashCode () doivent nécessairement prendre en compte les particularités de leurs superclasses. J'ai eu du succès à coder des appels aux méthodes equals () et hashCode () de la classe parente à partir des méthodes de la sous-classe.
En ce qui concerne la réponse acceptée de @CPerkins, je ne pense pas que le code equals () donné fonctionnera de manière fiable, car il est probable que la méthode super.equals () vérifie également l’égalité de classe. Une sous-classe et une super-classe n'auront pas des classes égales.
HomoSapiens#hashcode
suffira avec la réponse de CPerkins.
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 89 * hash + Objects.hash(name);
hash = 89 * hash + Objects.hash(faceBookNickname);
return hash;
}
Si vous souhaitez que les champs de ces parents (gender
, weight
, height
) soient en action, vous pouvez créer une instance réelle de type parent et l'utiliser. Dieu merci, ce n'est pas un cours abstrait.
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final HomoSapiens other = (HomoSapiens) obj;
if (!super.equals(new Hominidae(
other.gender, other.weight, other.height))) {
return false;
}
if (!Objects.equals(name, other.name)) return false;
if (!Objects.equals(faceBookNickname, other.faceBookNickname))
return false;
return true;
}
J'ajoute un moyen de (je pense) résoudre ceci. Le point clé est l’ajout d’une méthode qui vérifie l’égalité de façon approximative.
public class Parent {
public Parent(final String name) {
super(); this.name = name;
}
@Override
public int hashCode() {
return hash = 53 * 7 + Objects.hashCode(name);
}
@Override
public boolean equals(final Object obj) {
return equalsAs(obj) && getClass() == obj.getClass();
}
protected boolean equalsAs(final Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (!getClass().isAssignableFrom(obj.getClass())) return false;
final Parent other = (Parent) obj;
if (!Objects.equals(name, other.name)) return false;
return true;
}
private final String name;
}
Et voici la Child
.
public class Child extends Parent {
public Child(final String name, final int age) {
super(name); this.age = age;
}
@Override
public int hashCode() {
return hash = 31 * super.hashCode() + age;
}
@Override
public boolean equals(final Object obj) {
return super.equals(obj);
}
@Override
protected boolean equalsAs(final Object obj) {
if (!super.equalsAs(obj)) return false;
if (!getClass().isAssignableFrom(obj.getClass())) return false;
final Child other = (Child) obj;
if (age != other.age) return false;
return true;
}
private final int age;
}
Essai...
@Test(invocationCount = 128)
public void assertReflective() {
final String name = current().nextBoolean() ? "null" : null;
final int age = current().nextInt();
final Child x = new Child(name, age);
assertTrue(x.equals(x));
assertEquals(x.hashCode(), x.hashCode());
}
@Test(invocationCount = 128)
public void assertSymmetric() {
final String name = current().nextBoolean() ? "null" : null;
final int age = current().nextInt();
final Child x = new Child(name, age);
final Child y = new Child(name, age);
assertTrue(x.equals(y));
assertEquals(x.hashCode(), y.hashCode());
assertTrue(y.equals(x));
assertEquals(y.hashCode(), x.hashCode());
}
@Test(invocationCount = 128)
public void assertTransitive() {
final String name = current().nextBoolean() ? "null" : null;
final int age = current().nextInt();
final Child x = new Child(name, age);
final Child y = new Child(name, age);
final Child z = new Child(name, age);
assertTrue(x.equals(y));
assertEquals(x.hashCode(), y.hashCode());
assertTrue(y.equals(z));
assertEquals(y.hashCode(), z.hashCode());
assertTrue(x.equals(z));
assertEquals(x.hashCode(), z.hashCode());
}
Il semble que votre (super) classe parent ne remplace pas l'égalité. Si tel est le cas, vous devez comparer les champs de la classe parente lorsque vous substituez cette méthode à la sous-classe. Je suis d’accord pour dire que l’utilisation de la commune EqualsBuiler est la voie à suivre, mais vous devez faire attention à ne pas casser les parties symétrie/transative du contrat d’égalité.
Si votre sous-classe ajoute des attributs à la classe parente et si la classe parente n'est pas abstraite et remplace égale par égalité, vous allez avoir des ennuis. Dans ce scénario, vous devriez vraiment regarder la composition d'objet plutôt que l'héritage.
Je recommanderais fortement la section de Effective Java de Joshua Block à ce sujet. C'est complet et vraiment bien expliqué.
Je crois qu'ils ont maintenant une méthode qui ne fait que cela pour vous:
EqualsBuilder.reflectionEquals (this, o);
HashCodeBuilder.reflectionHashCode (this);
Il est à noter que la génération automatique IDE a peut-être pris en compte la super classe ,, à condition que les équivalents () et hashCode () de la super classe existent encore. C'est-à-dire, devrait générer automatiquement ces deux fonctions de super en premier, puis générer automatiquement de l'enfant. J'ai eu ci-dessous le bon exemple sous Intellj Idea:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
TActivityWrapper that = (TActivityWrapper) o;
return data != null ? data.equals(that.data) : that.data == null;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (data != null ? data.hashCode() : 0);
return result;
}
Le problème survient juste au moment où vous ne générez pas automatiquement de super en premier. Veuillez vérifier ci-dessus sous Netbeans.