Pregunta

¿Qué problemas/obstáculos se deben considerar al anular? equals y hashCode?

¿Fue útil?

Solución

La teoría (para los lingüistas y los aficionados a las matemáticas):

equals() (javadoc) debe definir una relación de equivalencia (debe ser reflexivo, simétrico, y transitivo).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 falso.

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

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

Cuando sea a.equals(b), entonces a.hashCode() debe ser igual que b.hashCode().

En la práctica:

Si anula uno, entonces debería anular el otro.

Utilice el mismo conjunto de campos que utiliza para calcular equals() computar hashCode().

Utilice las excelentes clases de ayuda. IgualesConstructor y Generador de códigos hash desde el Idioma Apache Commons 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();
    }
}

Recuerda también:

Cuando se utiliza un método basado en hash Recopilación o Mapa como Conjunto de hash, LinkedHashSet, HashMap, Tabla de picadillo, o Mapa de Hash débil, asegúrese de que el hashCode() de los objetos clave que coloque en la colección nunca cambie mientras el objeto esté en la colección.La forma infalible de garantizar esto es hacer que sus claves sean inmutables, que también tiene otros beneficios.

Otros consejos

Hay algunos problemas que vale la pena notar si estás tratando con clases que persisten usando un Mapeador de relaciones de objetos (ORM) como Hibernate, ¡si no pensabas que esto ya era irrazonablemente complicado!

Los objetos cargados de forma diferida son subclases

Si sus objetos persisten mediante un ORM, en muchos casos tendrá que tratar con servidores proxy dinámicos para evitar cargar objetos demasiado pronto desde el almacén de datos.Estos servidores proxy 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 de forma diferida tienen campos nulos

Los ORM suelen utilizar captadores para forzar la carga de objetos cargados de forma diferida.Esto significa que person.name será null si person tiene carga diferida, incluso si person.getName() fuerza la carga y devuelve "John Doe".En mi experiencia, esto surge más a menudo en hashCode() y equals().

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

Guardar un objeto cambiará su estado

Los objetos persistentes suelen utilizar un id campo para contener la clave del objeto.Este campo se actualizará automáticamente cuando se guarde un objeto por primera vez.No utilice 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 hace, cuando un objeto persiste, su hashCode cambios.Si el objeto está en un HashSet, "nunca" lo volverás a encontrar.

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

hashCode() debe utilizar el subconjunto de propiedades que no cambian de equals()

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

Esta afirmación es el resultado de equals() siendo la herencia hostil.La JLS (especificación del lenguaje Java) especifica que si A.equals(B) == true entonces B.equals(A) también debe regresar true.Si omite esa declaración heredando clases que anulan equals() (y cambiar 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));
        }
    }    

Haciendo 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.

Todo esto se ve muy bien, pero mira lo que pasa si intentamos 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 desea garantizar la condición simétrica.a=b si b=a y el principio de sustitución de Liskov llama super.equals(other) no sólo en el caso de B ejemplo, pero verifique después A instancia:

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 generará:

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

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

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

Resumen:

en su libro Guía eficaz del 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 al preservar el contrato igual". Tal no está de acuerdo.

Su solución es implementar equals() llamando a otro ciegamenteEquals() no simétrico en ambos sentidos.blindlyEquals() es anulado 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 todas las jerarquías de herencia si el Principio de sustitución de Liskov es estar satisfecho.

Todavía me sorprende que nadie 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 la superclase java.lang.Object.Necesitamos anularlos a objetos personalizados.

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

¡Divertirse!@.@

Hay un par de formas de comprobar la igualdad de clases antes de comprobar la igualdad de miembros, y creo que ambas son útiles en las circunstancias adecuadas.

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

Yo uso el número 1 en un final implementación de iguales, o al implementar una interfaz que prescribe un algoritmo para iguales (como el java.util Interfaces de colección: la forma correcta de consultar con (obj instanceof Set) o cualquier interfaz que estés implementando).Generalmente es una mala elección cuando se pueden anular los iguales porque eso rompe la propiedad de simetría.

La opción n.º 2 permite ampliar la clase de forma segura sin anular los iguales ni romper la simetría.

Si tu clase también es Comparable, el equals y compareTo Los métodos también deben ser consistentes.Aquí hay una plantilla para el método igual en un 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;
  }

}

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

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

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

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

La implementación predeterminada proporcionada es el método hashCode() en la clase Object que utiliza la dirección interna del objeto, la convierte en un número 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;

  }
}

Salida de código de ejemplo:

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 ¡viceversa!

Un problema que encontré es que dos objetos contienen referencias entre sí (un ejemplo es una relación padre/hijo con un método conveniente para el padre para obtener todos los hijos).
Este tipo de cosas son bastante comunes cuando se realizan asignaciones de Hibernación, por ejemplo.

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top