java - que - Reemplazando equals() y hashCode() en sub clases... considerando super campos
para que es el hashcode (10)
Bueno, HomoSapiens#hashcode
será suficiente con la respuesta 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 desea que los campos de esos padres ( gender
, weight
, height
) en acción, una forma es crear una instancia real de tipo padre y usarla. Gracias a Dios, no es una clase abstracta.
@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;
}
Estoy agregando una forma de (creo) resolver esto. El punto clave es agregar un método para verificar la igualdad.
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;
}
Y aquí viene el 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;
}
Pruebas...
@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());
}
¿Hay alguna regla específica sobre cómo hashCode()
y hashCode()
en sub clases considerando supercampos ? sabiendo que hay muchos parámetros: los supercampos son privados / públicos, con / sin getter ...
Por ejemplo, los Netbeans generados equals () y hashCode () no considerarán los supercampos ... y
new HomoSapiens("M", "80", "1.80", "Cammeron", "VeryHot").equals(
new HomoSapiens("F", "50", "1.50", "Cammeron", "VeryHot"))
devolverá verdadero :(
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 desea ver el Netbeans generado equals () y 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;
}
}
Creo que ahora tienen un método que solo hace esto por ti:
EqualsBuilder.reflectionEquals (this, o);
HashCodeBuilder.reflectionHashCode (this);
Debido a que la herencia rompe la encapsulación, las subclases que implementan equals () y hashCode () deben, necesariamente, tener en cuenta las peculiaridades de sus superclases. He tenido éxito al codificar llamadas a los métodos equals () y hashCode () de la clase padre de los métodos de la subclase.
En cuanto a la respuesta @CPerkins aceptada, no creo que el código equals () proporcionado funcione de manera confiable, debido a la probabilidad de que el método super.equals () también verifique la igualdad de clases. Una subclase y una superclase no tendrán clases iguales.
En términos generales, la implementación de iguales a través de subclases es difícil de mantener simétrica y transitiva.
Considere una superclase que comprueba los campos x
e y
, y las comprobaciones de las subclases para x
, y
y z
.
Entonces una Subclase == Superclase == Subclase donde z es diferente entre la primera instancia de Subclase y la segunda, violando la parte transitiva del contrato.
Esta es la razón por la cual la implementación típica de iguales verificará para getClass() != obj.getClass()
lugar de hacer un instanceof. En el ejemplo anterior, si SubClass o Superclass hace una instancia de verificación, rompería la simetría.
Por lo tanto, el resultado es que una subclase puede tener en cuenta super.equals (), pero también debe hacer su propia comprobación de getClass () para evitar los problemas anteriores y luego buscar también iguales en sus propios campos. Sería un pato extraño de una clase que cambiara su propio comportamiento de igualdad basado en campos específicos de la superclase en lugar de solo si la superclase devuelve igual.
Las reglas son:
- Es reflexivo: para cualquier valor de referencia no nulo x, x.equals (x) debería devolver verdadero.
- Es simétrico: para cualquier valor de referencia no nulo x e y, x.equals (y) debería devolver verdadero si y solo si y.equals (x) devuelve verdadero.
- Es transitivo: para cualquier valor de referencia no nulo x, y y z, si x.equals (y) devuelve verdadero y y.equals (z) devuelve verdadero, entonces x.equals (z) debe devolver verdadero.
- Es consistente: para cualquier valor de referencia no nulo xey, las invocaciones múltiples de x.equals (y) devuelven true o devuelven false de manera consistente, siempre que no se modifique la información utilizada en comparaciones iguales en los objetos.
- Para cualquier valor de referencia no nulo x, x.equals (null) debería devolver falso.
- En general, es necesario anular el método hashCode siempre que se anule este método, a fin de mantener el contrato general para el método hashCode, que establece que los objetos iguales deben tener códigos hash iguales.
de Object.equals() .
Por lo tanto, use los campos necesarios para cumplir las reglas.
Parece que su clase principal (super) no anula iguales. Si este es el caso, entonces necesita comparar los campos de la clase principal cuando anula este método en la subclase. Estoy de acuerdo en que usar Commons EqualsBuiler es el camino a seguir, pero debes tener cuidado de no romper las partes de simetría / transatividad del contrato de iguales.
Si su subclase agrega atributos a la clase principal y la clase principal no es abstracta y anula las equivalentes, tendrá problemas. En este escenario, realmente deberías mirar la composición del objeto en lugar de la herencia.
Recomiendo encarecidamente la sección en Effective Java de Joshua Block sobre esto. Es completo y muy bien explicado.
Prefiero usar EqualsBuilder (y HashcodeBuilder) del paquete commons-lang para hacer que mis métodos equals () y hashcode () sean mucho más fáciles de leer.
Ejemplo:
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();
}
Vale la pena señalar que la generación de auto IDE tal vez haya tenido en cuenta la superclase, siempre que aún existan equals () y hashCode () de superclase. Es decir, debe auto generar estas dos funciones de súper primero, y luego generar automáticamente el niño. Obtuve el siguiente ejemplo en 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;
}
El problema ocurre justo cuando no generas súper automáticamente en primer lugar. Por favor, ver más arriba en Netbeans.
Los niños no deberían examinar a los miembros privados de sus padres
Pero, obviamente , todos los campos importantes deben tenerse en cuenta para la igualdad y el hash.
Afortunadamente, usted puede satisfacer fácilmente ambas reglas.
Asumiendo que no estás atascado usando los equivalentes y códigos hash generados por NetBeans, puedes modificar el método equinos de Hominidae para usar la instancia de comparación en lugar de la igualdad de clases, y luego usarlo directamente. Algo como esto:
@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
}
Por supuesto, hashcode es fácil:
@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;
}
En serio, sin embargo: ¿qué pasa con que NetBeans no tome en cuenta los campos de superclase llamando a los métodos de superclase?