¿Qué problemas se deben tener en cuenta al anular equals y hashCode en Java?

¿Qué problemas / dificultades se deben tener en cuenta al anular equals y hashCode ?

La teoría (para los abogados del lenguaje y los matemáticamente inclinados):

equals() ( javadoc ) debe definir una relación de equivalencia (debe ser reflexiva , simétrica y transitiva ). Además, debe ser coherente (si los objetos no se modifican, entonces debe seguir devolviendo el mismo valor). Además, o.equals(null) siempre debe devolver false.

hashCode() ( javadoc ) también debe ser coherente (si el objeto no se modifica en términos de equals() , debe seguir devolviendo el mismo valor).

La relación entre los dos métodos es:

Siempre que a.equals(b) , entonces a.hashCode() debe ser el mismo que b.hashCode() .

En la práctica:

Si anulas uno, entonces debes anular el otro.

Use el mismo conjunto de campos que usa para calcular equals() para calcular hashCode() .

Utilice las excelentes clases auxiliares EqualsBuilder y HashCodeBuilder de la biblioteca Apache Commons Lang . Un ejemplo:

 public class Person { private String name; private int age; // ... @Override public int hashCode() { return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers // if deriving: appendSuper(super.hashCode()). append(name). append(age). toHashCode(); } @Override public boolean equals(Object obj) { if (!(obj instanceof Person)) return false; if (obj == this) return true; Person rhs = (Person) obj; return new EqualsBuilder(). // if deriving: appendSuper(super.equals(obj)). append(name, rhs.name). append(age, rhs.age). isEquals(); } } 

Recuerde también:

Al usar una colección o un mapa basado en hash como HashSet , LinkedHashSet , HashMap , Hashtable o WeakHashMap , asegúrese de que hashCode () de los objetos clave que coloca en la colección nunca cambie mientras el objeto está en la colección. La forma a prueba de balas para garantizar esto es hacer que sus llaves sean inmutables, lo que también tiene otros beneficios .

Hay algunos problemas que vale la pena notar si se trata de clases que se conservan utilizando un Object-Relationship Mapper (ORM) como Hibernate, ¡si no creía que esto ya era irrazonablemente complicado!

Los objetos cargados perezosos son subclases

Si sus objetos persisten utilizando un ORM, en muchos casos se tratará con proxies dynamics para evitar cargar objetos demasiado pronto desde el almacén de datos. Estos proxies se implementan como subclases de su propia clase. Esto significa que this.getClass() == o.getClass() devolverá false . Por ejemplo:

 Person saved = new Person("John Doe"); Long key = dao.save(saved); dao.flush(); Person retrieved = dao.retrieve(key); saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy 

Si está tratando con un ORM, usar o instanceof Person es lo único que se comportará correctamente.

Los objetos cargados perezosos tienen campos nulos

Los ORM generalmente usan los captadores para forzar la carga de objetos cargados perezosos. Esto significa que person.name será null si la person está cargada de forma diferida, incluso si person.getName() fuerza la carga y devuelve “John Doe”. En mi experiencia, esto aparece más a menudo en hashCode() y equals() .

Si está tratando con un ORM, asegúrese de usar siempre getters, y nunca referencias de campo en hashCode() y equals() .

Guardar un objeto cambiará su estado

Los objetos persistentes a menudo usan un campo de id para mantener la clave del objeto. Este campo se actualizará automáticamente cuando se guarda un objeto por primera vez. No use un campo de identificación en hashCode() . Pero puedes usarlo en equals() .

Un patrón que uso a menudo es

 if (this.getId() == null) { return this == other; } else { return this.getId().equals(other.getId()); } 

Pero: no puede incluir getId() en hashCode() . Si lo haces, cuando un objeto persiste, su hashCode cambia. Si el objeto está en un HashSet , “nunca” lo encontrará de nuevo.

En mi ejemplo Person , probablemente usaría getName() para hashCode y getId() más getName() (solo para paranoia) para equals() . Está bien si hay algún riesgo de “colisiones” para hashCode() , pero nunca está bien para equals() .

hashCode() debería usar el subconjunto de propiedades no cambiante de equals()

Una aclaración sobre obj.getClass() != getClass() .

Esta afirmación es el resultado de que equals() es una herencia antipática. El JLS (especificación del lenguaje Java) especifica que si A.equals(B) == true entonces B.equals(A) también debe devolver true . Si omite esa instrucción heredando clases que anulan equals() (y cambian su comportamiento) se romperá esta especificación.

Considere el siguiente ejemplo de lo que sucede cuando se omite la statement:

  class A { int field1; A(int field1) { this.field1 = field1; } public boolean equals(Object other) { return (other != null && other instanceof A && ((A) other).field1 == field1); } } class B extends A { int field2; B(int field1, int field2) { super(field1); this.field2 = field2; } public boolean equals(Object other) { return (other != null && other instanceof B && ((B)other).field2 == field2 && super.equals(other)); } } 

Hacer new A(1).equals(new A(1)) Además, el new B(1,1).equals(new B(1,1)) da como verdadero, como debería ser.

Esto se ve muy bien, pero mira lo que sucede si tratamos de usar ambas clases:

 A a = new A(1); B b = new B(1,1); a.equals(b) == true; b.equals(a) == false; 

Obviamente, esto está mal.

Si quieres asegurarte de la condición simétrica. a = b si b = a y el principio de sustitución de Liskov llama a super.equals(other) no solo en el caso de la instancia B , sino que también busca A instancia A :

 if (other instanceof B ) return (other != null && ((B)other).field2 == field2 && super.equals(other)); if (other instanceof A) return super.equals(other); else return false; 

Que dará salida:

 a.equals(b) == true; b.equals(a) == true; 

Donde, si a no es una referencia de B , entonces podría ser a ser una referencia de la clase A (porque la extiendes), en este caso llamas a super.equals() también .

Para una implementación amigable con la herencia, consulte la solución de Tal Cohen, ¿Cómo implemento correctamente el método equals ()?

Resumen:

En su libro Effective Java Programming Language Guide (Addison-Wesley, 2001), Joshua Bloch afirma que “Simplemente no hay forma de extender una clase instanciable y agregar un aspecto conservando el mismo contrato”. Tal no está de acuerdo.

Su solución es implementar equals () llamando a otro no simétrico blindlyEquals () en ambos sentidos. blindlyEquals () se reemplaza por subclases, equals () se hereda y nunca se anula.

Ejemplo:

 class Point { private int x; private int y; protected boolean blindlyEquals(Object o) { if (!(o instanceof Point)) return false; Point p = (Point)o; return (px == this.x && py == this.y); } public boolean equals(Object o) { return (this.blindlyEquals(o) && o.blindlyEquals(this)); } } class ColorPoint extends Point { private Color c; protected boolean blindlyEquals(Object o) { if (!(o instanceof ColorPoint)) return false; ColorPoint cp = (ColorPoint)o; return (super.blindlyEquals(cp) && cp.color == this.color); } } 

Tenga en cuenta que equals () debe funcionar entre las jerarquías de herencia si se quiere cumplir el Principio de sustitución de Liskov .

Todavía asombrado de que ninguno haya recomendado la biblioteca de guayaba para esto.

  //Sample taken from a current working project of mine just to illustrate the idea @Override public int hashCode(){ return Objects.hashCode(this.getDate(), this.datePattern); } @Override public boolean equals(Object obj){ if ( ! obj instanceof DateAndPattern ) { return false; } return Objects.equal(((DateAndPattern)obj).getDate(), this.getDate()) && Objects.equal(((DateAndPattern)obj).getDate(), this.getDatePattern()); } 

Hay dos métodos en superclase como java.lang.Object. Necesitamos anularlos a un objeto personalizado.

 public boolean equals(Object obj) public int hashCode() 

Los objetos iguales deben producir el mismo código hash siempre que sean iguales, sin embargo, los objetos desiguales no necesitan producir códigos hash distintos.

 public class Test { private int num; private String data; public boolean equals(Object obj) { if(this == obj) return true; if((obj == null) || (obj.getClass() != this.getClass())) return false; // object must be Test at this point Test test = (Test)obj; return num == test.num && (data == test.data || (data != null && data.equals(test.data))); } public int hashCode() { int hash = 7; hash = 31 * hash + num; hash = 31 * hash + (null == data ? 0 : data.hashCode()); return hash; } // other methods } 

Si desea obtener más, consulte este enlace en http://www.javaranch.com/journal/2002/10/equalhash.html

Este es otro ejemplo, http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html

¡Que te diviertas! @. @

Hay un par de maneras de hacer su verificación de igualdad de clases antes de verificar la igualdad de los miembros, y creo que ambas son útiles en las circunstancias correctas.

  1. Use el operador instanceof .
  2. Use this.getClass().equals(that.getClass()) .

Uso el n. ° 1 en una implementación final igual o cuando implemento una interfaz que prescribe un algoritmo para iguales (como las interfaces de colección java.util , la forma correcta de consultar con (obj instanceof Set) o cualquier interfaz que esté implementando) . En general, es una mala elección cuando se pueden anular iguales porque eso rompe la propiedad de simetría.

La opción n. ° 2 permite que la clase se extienda con seguridad sin anular iguales o romper la simetría.

Si su clase también es Comparable , los métodos equals y compareTo deberían ser consistentes. Aquí hay una plantilla para el método equals en una clase Comparable :

 final class MyClass implements Comparable { … @Override public boolean equals(Object obj) { /* If compareTo and equals aren't final, we should check with getClass instead. */ if (!(obj instanceof MyClass)) return false; return compareTo((MyClass) obj) == 0; } } 

Para los iguales, busque en Secrets of Equals por Angelika Langer . Yo la amo mucho. Ella también es una gran pregunta frecuente sobre generics en Java . Vea sus otros artículos aquí (desplácese hacia abajo a “Core Java”), donde también continúa con la Parte-2 y “comparación de tipos mixtos”. ¡Diviértete leyéndolos!

El método equals () se usa para determinar la igualdad de dos objetos.

como int value de 10 es siempre igual a 10. Pero este método equals () trata de la igualdad de dos objetos. Cuando decimos object, tendrá propiedades. Para decidir sobre la igualdad, esas propiedades se consideran. No es necesario que todas las propiedades se tengan en cuenta para determinar la igualdad y con respecto a la definición de clase y contexto se puede decidir. Entonces el método equals () puede ser anulado.

siempre debemos anular el método hashCode () siempre que anulemos el método equals (). Si no, ¿qué pasará? Si usamos hashtables en nuestra aplicación, no se comportará como se espera. Como hashCode se utiliza para determinar la igualdad de valores almacenados, no devolverá el valor correspondiente correcto para una clave.

La implementación predeterminada dada es el método hashCode () en la clase Object usa la dirección interna del objeto y la convierte en un entero y la devuelve.

 public class Tiger { private String color; private String stripePattern; private int height; @Override public boolean equals(Object object) { boolean result = false; if (object == null || object.getClass() != getClass()) { result = false; } else { Tiger tiger = (Tiger) object; if (this.color == tiger.getColor() && this.stripePattern == tiger.getStripePattern()) { result = true; } } return result; } // just omitted null checks @Override public int hashCode() { int hash = 3; hash = 7 * hash + this.color.hashCode(); hash = 7 * hash + this.stripePattern.hashCode(); return hash; } public static void main(String args[]) { Tiger bengalTiger1 = new Tiger("Yellow", "Dense", 3); Tiger bengalTiger2 = new Tiger("Yellow", "Dense", 2); Tiger siberianTiger = new Tiger("White", "Sparse", 4); System.out.println("bengalTiger1 and bengalTiger2: " + bengalTiger1.equals(bengalTiger2)); System.out.println("bengalTiger1 and siberianTiger: " + bengalTiger1.equals(siberianTiger)); System.out.println("bengalTiger1 hashCode: " + bengalTiger1.hashCode()); System.out.println("bengalTiger2 hashCode: " + bengalTiger2.hashCode()); System.out.println("siberianTiger hashCode: " + siberianTiger.hashCode()); } public String getColor() { return color; } public String getStripePattern() { return stripePattern; } public Tiger(String color, String stripePattern, int height) { this.color = color; this.stripePattern = stripePattern; this.height = height; } } 

Ejemplo de salida de código:

 bengalTiger1 and bengalTiger2: true bengalTiger1 and siberianTiger: false bengalTiger1 hashCode: 1398212510 bengalTiger2 hashCode: 1398212510 siberianTiger hashCode: –1227465966 

Lógicamente tenemos:

a.getClass().equals(b.getClass()) && a.equals(b)a.hashCode() == b.hashCode()

¡Pero no al revés!

Una de las preguntas que he encontrado es donde dos objetos contienen referencias entre sí (un ejemplo es una relación padre / hijo con un método de conveniencia en el padre para obtener todos los hijos).
Este tipo de cosas son bastante comunes al hacer asignaciones de Hibernate, por ejemplo.

Si incluye ambos extremos de la relación en su código hash o pruebas equivalentes, es posible entrar en un bucle recursivo que finaliza en una StackOverflowException.
La solución más simple es no incluir la colección getChildren en los métodos.