¿Por qué necesito anular los métodos iguales y hashCode en Java?

Resuelto Batakj asked hace 14 años • 31 respuestas

Recientemente leí este documento de Developer Works .

El documento trata sobre definir hashCode()y equals()de manera efectiva y correcta, sin embargo, no puedo entender por qué necesitamos anular estos dos métodos.

¿Cómo puedo tomar la decisión de implementar estos métodos de manera eficiente?

Batakj avatar Feb 15 '10 18:02 Batakj
Aceptado

Joshua Bloch dice sobre Java efectivo

Debes anular hashCode() en cada clase que anule equals(). No hacerlo resultará en una violación del contrato general de Object.hashCode(), lo que impedirá que su clase funcione correctamente junto con todas las colecciones basadas en hash, incluidas HashMap, HashSet y Hashtable.

Intentemos entenderlo con un ejemplo de lo que sucedería si anulamos equals()sin anular hashCode()e intentamos usar un archivo Map.

Digamos que tenemos una clase como esta y que dos objetos MyClassson iguales si importantFieldson iguales (con eclipse hashCode()y equals()generado por él)

public class MyClass {
    private final String importantField;
    private final String anotherField;

    public MyClass(final String equalField, final String anotherField) {
        this.importantField = equalField;
        this.anotherField = anotherField;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((importantField == null) ? 0 : importantField.hashCode());
        return result;
    }

    @Override
    public boolean equals(final Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final MyClass other = (MyClass) obj;
        if (importantField == null) {
            if (other.importantField != null)
                return false;
        } else if (!importantField.equals(other.importantField))
            return false;
        return true;
    }
}

Imagina que tienes esto

MyClass first = new MyClass("a","first");
MyClass second = new MyClass("a","second");

Anular sóloequals

Si solo equalsse anula, cuando llame myMap.put(first,someValue)primero se aplicará el hash a algún depósito y cuando lo llame, myMap.put(second,someOtherValue)se aplicará el hash a algún otro depósito (ya que tienen un diferente hashCode). Entonces, aunque son iguales, como no hacen hash en el mismo depósito, el mapa no puede darse cuenta y ambos permanecen en el mapa.


Aunque no es necesario anular equals()si anulamos hashCode(), veamos qué pasaría en este caso particular donde sabemos que dos objetos MyClassson iguales si importantFieldson iguales pero no anulamos equals().

Anular sólohashCode

Si solo anula hashCode, cuando llama, myMap.put(first,someValue)toma primero, lo calcula hashCodey lo almacena en un depósito determinado. Luego, cuando llame, myMap.put(second,someOtherValue)debe reemplazar el primero por el segundo según la documentación del mapa porque son iguales (según los requisitos comerciales).

Pero el problema es que igual no se redefinió, por lo que cuando el mapa realiza un hash secondy recorre el depósito buscando si hay un objeto kque second.equals(k)sea verdadero, no encontrará ninguno como second.equals(first)será false.

Espero que haya sido claro

Lombo avatar Feb 15 '2010 11:02 Lombo

Las colecciones como HashMapy HashSetusan un valor de código hash de un objeto para determinar cómo debe almacenarse dentro de una colección, y el código hash se usa nuevamente para ubicar el objeto en su colección.

La recuperación de hash es un proceso de dos pasos:

  1. Encuentra el cubo correcto (usando hashCode())
  2. Busque en el depósito el elemento correcto (usando equals())

Aquí hay un pequeño ejemplo de por qué debemos anular equals()y hashcode().

Considere una Employeeclase que tiene dos campos: edad y nombre.

public class Employee {

    String name;
    int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;
        if (!(obj instanceof Employee))
            return false;
        Employee employee = (Employee) obj;
        return employee.getAge() == this.getAge()
                && employee.getName() == this.getName();
    }

    // commented    
    /*  @Override
        public int hashCode() {
            int result=17;
            result=31*result+age;
            result=31*result+(name!=null ? name.hashCode():0);
            return result;
        }
     */
}

Ahora cree una clase, inserte Employeeun objeto en HashSety pruebe si ese objeto está presente o no.

public class ClientTest {
    public static void main(String[] args) {
        Employee employee = new Employee("rajeev", 24);
        Employee employee1 = new Employee("rajeev", 25);
        Employee employee2 = new Employee("rajeev", 24);

        HashSet<Employee> employees = new HashSet<Employee>();
        employees.add(employee);
        System.out.println(employees.contains(employee2));
        System.out.println("employee.hashCode():  " + employee.hashCode()
        + "  employee2.hashCode():" + employee2.hashCode());
    }
}

Imprimirá lo siguiente:

false
employee.hashCode():  321755204  employee2.hashCode():375890482

Ahora descomente hashcode()el método, ejecute lo mismo y el resultado sería:

true
employee.hashCode():  -938387308  employee2.hashCode():-938387308

Ahora, ¿puedes ver por qué si dos objetos se consideran iguales, sus códigos hash también deben ser iguales? De lo contrario, nunca podrá encontrar el objeto, ya que el método de código hash predeterminado en la clase Objeto prácticamente siempre genera un número único para cada objeto, incluso si el equals()método se anula de tal manera que dos o más objetos se consideran iguales. . No importa cuán iguales sean los objetos si sus códigos hash no reflejan eso. Entonces, una vez más: si dos objetos son iguales, sus códigos hash también deben ser iguales.

rajeev pani.. avatar Nov 27 '2014 05:11 rajeev pani..

Debes anular hashCode() en cada clase que anule equals(). No hacerlo resultará en una violación del contrato general de Object.hashCode(), lo que impedirá que su clase funcione correctamente junto con todas las colecciones basadas en hash, incluidas HashMap, HashSet y Hashtable.


   de Java efectivo , por Joshua Bloch

Al definirlo equals()y hashCode()hacerlo de manera consistente, puede mejorar la usabilidad de sus clases como claves en colecciones basadas en hash. Como explica el documento API para hashCode: "Este método se admite en beneficio de tablas hash como las proporcionadas por java.util.Hashtable".

La mejor respuesta a su pregunta sobre cómo implementar estos métodos de manera eficiente es sugerirle que lea el Capítulo 3 de Java efectivo .

JuanZe avatar Feb 15 '2010 11:02 JuanZe