Vamos primero con los ejemplos para aquellos que llegan acá buscando la solución sin leer mucho. Al final está la teoría.
Conozco dos muy buenas bibliotecas con utilidades para implementar equals y hashCode, así que doy un ejemplo con cada una de ellas.
Ejemplo usando jakarta commons-lang
import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; public class MyClass { private String field1; private int field2; private double field3; @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof MyClass)) { return false; } MyClass other = (MyClass) obj; // Now we compare field by field return new EqualsBuilder() .append(this.field1, other.field1) .append(this.field2, other.field2) .append(this.field3, other.field3) .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder() .append(field1) .append(field2) .append(field3) .toHashCode(); } } class MySubClass extends MyClass { private String field4; @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof MySubClass)) { return false; } MySubClass other = (MySubClass) obj; // Now we compare field by field return new EqualsBuilder() .appendSuper(super.equals(obj)) // Note the call to super .append(this.field4, other.field4) .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder() .appendSuper(super.hashCode()) // Note the call to super .append(field4) .toHashCode(); } }
Ejemplo usando Guava
import com.google.common.base.Objects; public class MyClass { private String field1; private int field2; private double field3; @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof MyClass)) { return false; } MyClass other = (MyClass) obj; // Now we compare field by field return Objects.equal(this.field1, other.field1) && Objects.equal(this.field2, other.field2) && Objects.equal(this.field3, other.field3); } @Override public int hashCode() { return Objects.hashCode(field1, field2, field3); } } class MySubClass extends MyClass { private String field4; @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof MySubClass)) { return false; } MySubClass other = (MySubClass) obj; // Now we compare field by field return super.equals(obj) // Note the call to super && Objects.equal(this.field4, other.field4); } @Override public int hashCode() { return Objects.hashCode(super.hashCode(), field4); // Note the call to super } }
La teoría
La regla es que si se implementa equals(), también se debe implementar hashCode() porque siempre se debe cumplir lo siguiente:- Si dos objetos son iguales, deben tener el mismo hashCode.
- Si dos objetos tienen diferente hashCode entonces deben ser distintos.
En el caso del equals, también es necesario que se cumpla lo siguiente:
Dados los objetos A, B y C:
- Si A.equals(B) entonces B.equals(A) y viceversa
- A.equals(A) debe ser true
- A.equals(null) debe ser false
- Si A.equals(B) y B.equals(C), entonces debe cumplirse que A.equals(C)
La única regla que no se cumple del todo con los ejemplos es la primera, en el caso del equals de las subclases.
@Test public void testEqualsObject() { MyClass a = new MyClass(); MyClass b = new MySubClass(); assertTrue(a.equals(b)); // Returns true and perhaps it shouldn't assertFalse(b.equals(a)); // Returns false }Una alternativa es implementar el comienzo del equals así:
@Override public boolean equals(Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (obj.getClass() != getClass()) { return false; } MyClass other = (MyClass) obj; // Now we compare field by field //... }
La desventaja es que las subclasses deberían implementar el equals teniendo en cuenta los campos de la superclase sin poder llamar a super.equals().
Como sea, es algo a tener en cuenta y cada uno deberá balancear las ventajas y desventajas de cada alternativa.
Apéndice: Si querés incluir estas bibliotecas en maven
<dependency> <groupid>org.apache.commons</groupid> <artifactid>commons-lang3</artifactid> <version>3.1</version> </dependency> <dependency> <groupid>com.google.guava</groupid> <artifactid>guava</artifactid> <version>13.0</version> </dependency>