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


Qué problemas / riesgos deben tenerse en cuenta al anular equals y hashCode?


617
2017-08-26 08:50


origen


Respuestas:


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

equals() (javadoc) debe definir una relación de equivalencia (debe ser reflexivo, simétricoy transitivo) Además, debe ser consistente (si los objetos no son modificados, entonces debe seguir devolviendo el mismo valor). Además, o.equals(null) siempre debe devolver falso.

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

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

Cuando 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() computar hashCode().

Usa las excelentes clases de ayuda EqualsBuilder y HashCodeBuilder desde el Apache Commons Lang biblioteca. 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:

Cuando se usa un hash basado en Colección o Mapa como HashSet, LinkedHashSet, HashMap, Tabla de picadillo, 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 asegurar esto es hacer que tus llaves sean inmutables, que también tiene otros beneficios.


1370
2017-11-02 02:58



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 dinámicos para evitar cargar objetos demasiado pronto desde el almacén de datos. Estos proxies se implementan como subclases de su propia clase. Esto significa quethis.getClass() == o.getClass() regresará 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, usando 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 estarán null Si person está cargado de forma perezosa, 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 una id campo 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 puedes incluir getId() en hashCode(). Si lo haces, cuando un objeto persiste, es hashCode cambios. Si el objeto está en una HashSet, "nunca" volverás a encontrarlo.

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

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


281
2017-08-28 13:16



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

Esta declaración es el resultado de equals() siendo herencia hostil. El JLS (especificación de lenguaje Java) especifica que si A.equals(B) == true entonces B.equals(A) también debe regresar true. Si omite esa declaración, hereda clases que anulan equals() (y cambia su comportamiento) romperá esta especificación.

Considere el siguiente ejemplo de lo que sucede cuando se omite la declaración:

    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));
        }
    }    

Obra new A(1).equals(new A(1)) También, new B(1,1).equals(new B(1,1)) El resultado es 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 la llamada al principio de sustitución Liskov super.equals(other) no solo en el caso de B instancia, pero verifique después de A ejemplo:

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 un ser una referencia de clase A (porque lo extiendes), en este caso llamas super.equals()  también.


79
2017-09-11 03:06



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 Guía efectiva de lenguaje de programación Java (Addison-Wesley, 2001), Joshua Bloch afirma que "Simplemente no hay forma de extender una clase instanciable y agregar un aspecto mientras se preserva el contrato igual". 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 (p.x == this.x && p.y == 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 en las jerarquías de herencia si Principio de sustitución de Liskov es estar satisfecho


41
2018-02-12 07:17



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());
    }

31
2017-12-20 06:14



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 como 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! @. @


25
2017-08-28 18:25



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. Utilizar el instanceof operador.
  2. Utilizar this.getClass().equals(that.getClass()).

Yo uso el # 1 en una final es igual a la implementación, o al implementar una interfaz que prescribe un algoritmo para iguales (como el java.util interfaces de recopilación: la forma correcta de verificarlo con (obj instanceof Set) o la interfaz que estés 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 tu clase también es Comparable, el equals y compareTo los métodos también deberían ser consistentes. Aquí hay una plantilla para el método igual en una Comparable clase:

final class MyClass implements Comparable<MyClass>
{

  …

  @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;
  }

}

18
2018-02-27 22:05



Para iguales, mira dentro Secretos de iguales por Angelika Langer. Yo la amo mucho. Ella también es una gran pregunta frecuente sobre Genéricos en Java. Ver sus otros artículos aquí (desplácese hasta "Core Java"), donde también continúa con la Parte-2 y "comparación de tipos mixtos". ¡Diviértete leyéndolos!


15
2017-10-24 10:56