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

383

Recientemente leí este documento de Developer Works .

El documento se trata de definir hashCode()y de manera equals()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?

Shashi
fuente
44
Hay dos excelentes artículos en programación.guía que explican exactamente esto: ¿ Cuándo debería anular iguales? y por qué siempre debe anular hashCode al anular iguales . (Advertencia, la respuesta aceptada es realmente incorrecta.)
aioobe
La anulación de mayúsculas y minúsculas solo es igual: dos mismos objetos tendrán un código hash diferente = los mismos objetos van en un cubo diferente (duplicación). Anulación de mayúsculas y minúsculas solo hashcode: dos mismos objetos tendrán el mismo hashcode = el mismo objeto irá en el mismo depósito (duplicación).
VdeX

Respuestas:

524

Joshua Bloch dice sobre Java efectivo

Debe anular hashCode () en cada clase que anule equals (). De lo contrario, se infringirá el contrato general de Object.hashCode (), lo que evitará que su clase funcione correctamente junto con todas las colecciones basadas en hash, incluidos HashMap, HashSet y Hashtable.

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

Digamos que tenemos una clase como esta y que dos objetos de MyClassson iguales si su importantFieldes igual (con hashCode()y equals()generado por eclipse)

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

    public String getEqualField() {
        return importantField;
    }

    public String getAnotherField() {
        return 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;
    }

}

Anular solo equals

Si solo equalsse anula, entonces cuando llame myMap.put(first,someValue)primero se convertirá en hash a algún segmento y cuando lo llame myMap.put(second,someOtherValue)se dividirá en hash a otro segmento (ya que tienen un valor diferente hashCode). Entonces, aunque son iguales, ya que no se dividen en el mismo cubo, el mapa no puede darse cuenta y ambos permanecen en el mapa.


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

Anular solo hashCode

Imagina que tienes esto

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

Si solo anula hashCode, cuando llame myMap.put(first,someValue)primero toma, calcula su hashCodey lo almacena en un cubo determinado. Luego, cuando lo llame myMap.put(second,someOtherValue), debe reemplazar primero con el segundo según la Documentación del mapa porque son iguales (de acuerdo con los requisitos comerciales).

Pero el problema es que igual no se redefinió, por lo que cuando el mapa se desplaza seconde itera a través del cubo buscando si hay un objeto ktal que second.equals(k)sea ​​cierto, no encontrará ninguno como second.equals(first)lo será false.

Espero que sea claro

Lombo
fuente
55
¿Puedes por favor elaborar un poco más, en el segundo caso, por qué el segundo objeto debe ir en otro cubo?
Hussain Akhtar Wahid 'Ghouri'
57
No me gusta esta respuesta porque sugiere que no puede anular hashCode () sin anular equals (), lo que simplemente no es cierto. Dice que su código de ejemplo (la parte "anular solo el código hash") no funcionará porque define sus dos objetos como iguales, pero, lo siento, esta definición solo está en su cabeza. En su primer ejemplo, tiene dos objetos no iguales con el mismo hashCode, y eso es perfectamente legal. Entonces, la razón por la que necesita anular equals () no es porque ya haya anulado hashCode (), sino porque quiere mover su definición "equals" de su cabeza al código.
user2543253
11
if you think you need to override one, then you need to override both of themEstá Mal. Debe anular hashCodesi su clase anula equalspero revertir no es cierto.
akhil_mittal
44
Creo que está totalmente bien anular solo hashCode () sin anular equals () también. También está escrito en Java efectivo : books.google.fr/…
Johnny
2
@PhantomReference, tenga en cuenta que solo se anula equals violaría el contrato enunciado en el javadoc de Object: "Si dos objetos son iguales de acuerdo con el equals(Object)método, entonces llamar al hashCodemétodo en cada uno de los dos objetos debe producir el mismo resultado entero". Claro, no todas las partes de todos los contratos se ejercen en todos los códigos, pero aún así, formalmente hablando, es una violación y consideraría que es un error a la espera de suceder.
aioobe
264

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 cubo el elemento correcto (usando equals())

Aquí hay un pequeño ejemplo de por qué deberíamos 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 un Employeeobjeto en ay HashSetpruebe 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 el hashcode()método, ejecute lo mismo y la salida sería:

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

Ahora, ¿puede 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 presenta 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 hashcode s no reflejan eso. Entonces, una vez más: si dos objetos son iguales, su códigos hash también deben ser iguales.

rajeev pani ..
fuente
44
Ejemplo perfecto. Claramente demostró la diferencia!
coderpc
3
agradable explicado @rajeev
VdeX
2
@VikasVerma igual a objeto tendrá un código hash igual no significa que un objeto desigual tendrá un código hash desigual. ¿Qué pasa si los objetos son realmente diferentes, pero su código hash es el mismo?
Ravi
1
explicó muy bien :)
Rahul
44
respuesta mucho mejor que la respuesta aceptada! Gracias
driftwood
50

Debe anular hashCode () en cada clase que anule equals (). De lo contrario, se infringirá el contrato general de Object.hashCode (), lo que evitará que su clase funcione correctamente junto con todas las colecciones basadas en hash, incluidos HashMap, HashSet y Hashtable.


   de Effective Java , de Joshua Bloch

Al definir equals()y de forma hashCode()coherente, puede mejorar la usabilidad de sus clases como claves en colecciones basadas en hash. Como explica el documento de API para hashCode: "Este método es compatible 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 Effective Java .

JuanZe
fuente
44
Esta es la respuesta correcta. Corolario es, por supuesto, que si nunca usa la clase en una colección basada en hash, entonces no importa que no haya implementado hashCode().
delgado
1
En casos más complejos, nunca se sabe si las colecciones que usa están usando hash, así que manténgase alejado de "no importa que no haya implementado hashCode ()"
Victor Sergienko
1
¿Puedo anular hashCode () sin anular equals ()?
Johnny
@StasS, sí, contrario a lo que dice la respuesta aceptada. Ver explicación en la segunda parte de este artículo: ¿Por qué siempre se debe anular hashCode cuando anulando iguales
aioobe
22

En pocas palabras, el método igual en la comprobación de objetos para la igualdad de referencia, donde dos instancias de su clase aún podrían ser semánticamente iguales cuando las propiedades son iguales. Esto es importante, por ejemplo, cuando coloca sus objetos en un contenedor que utiliza igual y hashcode, como HashMap y Set . Digamos que tenemos una clase como:

public class Foo {
    String id;
    String whatevs;

    Foo(String id, String whatevs) {
        this.id = id;
        this.whatevs = whatevs;
    }
}

Creamos dos instancias con la misma identificación :

Foo a = new Foo("id", "something");
Foo b = new Foo("id", "something else");

Sin anular iguales, estamos obteniendo:

  • a.equals (b) es falso porque son dos instancias diferentes
  • a.equals (a) es cierto ya que es la misma instancia
  • b.equals (b) es cierto ya que es la misma instancia

¿Correcto? Bueno, tal vez, si esto es lo que quieres. Pero supongamos que queremos que los objetos con la misma identificación sean el mismo objeto, independientemente de si se trata de dos instancias diferentes. Anulamos los iguales (y el código hash):

public class Foo {
    String id;
    String whatevs;

    Foo(String id, String whatevs) {
        this.id = id;
        this.whatevs = whatevs;
    }

    @Override
    public boolean equals(Object other) {
        if (other instanceof Foo) {
            return ((Foo)other).id.equals(this.id);   
        }
    }

    @Override
    public int hashCode() {
        return this.id.hashCode();
    }
}

En cuanto a la implementación de equals y hashcode, puedo recomendar el uso de los métodos de ayuda de Guava

crunchdog
fuente
20

La identidad no es igualdad.

  • operador igual == identidad de prueba del .
  • equals(Object obj) El método compara la prueba de igualdad (es decir, necesitamos distinguir la igualdad anulando el método)

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

Primero tenemos que entender el uso del método igual.

Para identificar diferencias entre dos objetos, necesitamos anular el método igual.

Por ejemplo:

Customer customer1=new Customer("peter");
Customer customer2=customer1;
customer1.equals(customer2); // returns true by JVM. i.e. both are refering same Object
------------------------------
Customer customer1=new Customer("peter");
Customer customer2=new Customer("peter");
customer1.equals(customer2); //return false by JVM i.e. we have two different peter customers.

------------------------------
Now I have overriden Customer class equals method as follows:
 @Override
    public boolean equals(Object obj) {
        if (this == obj)   // it checks references
            return true;
        if (obj == null) // checks null
            return false;
        if (getClass() != obj.getClass()) // both object are instances of same class or not
            return false;
        Customer other = (Customer) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name)) // it again using bulit in String object equals to identify the difference 
            return false;
        return true; 
    }
Customer customer1=new Customer("peter");
Customer customer2=new Customer("peter");
Insteady identify the Object equality by JVM, we can do it by overring equals method.
customer1.equals(customer2);  // returns true by our own logic

Ahora el método hashCode puede entender fácilmente.

hashCode produce un número entero para almacenar objetos en estructuras de datos como HashMap , HashSet .

Supongamos que tenemos el método de anulación igual Customerque el anterior,

customer1.equals(customer2);  // returns true by our own logic

Mientras trabajamos con la estructura de datos cuando almacenamos objetos en cubos (el cubo es un nombre elegante para la carpeta). Si utilizamos la técnica hash incorporada, para los dos clientes anteriores genera dos códigos hash diferentes. Así que estamos almacenando el mismo objeto idéntico en dos lugares diferentes. Para evitar este tipo de problemas, debemos anular el método hashCode también basado en los siguientes principios.

  • las instancias no iguales pueden tener el mismo código hash.
  • instancias iguales deberían devolver el mismo código hash.
Premraj
fuente
3
Esto es lo que estaba buscando desde la última hora. Awesome mate (y)
Adnan
13

Ok, déjenme explicar el concepto en palabras muy simples.

En primer lugar, desde una perspectiva más amplia, tenemos colecciones, y el hashmap es una de las estructuras de datos en las colecciones.

Para entender por qué tenemos que anular los métodos de igual y hashcode, si es necesario primero entender qué es hashmap y qué hace.

Un hashmap es una estructura de datos que almacena pares de datos de valores clave en forma de matriz. Digamos a [], donde cada elemento en 'a' es un par de valores clave.

Además, cada índice de la matriz anterior puede vincularse con una lista que tenga más de un valor en un índice.

Ahora, ¿por qué se usa un hashmap? Si tenemos que buscar entre una gran matriz, buscar en cada una de ellas si no será eficiente, entonces, ¿qué técnica de hash nos dice que preprocesemos la matriz con cierta lógica y agrupemos los elementos basados ​​en esa lógica, es decir, Hashing?

por ejemplo: tenemos una matriz 1,2,3,4,5,6,7,8,9,10,11 y aplicamos una función hash mod 10, de modo que 1,11 se agruparán. Entonces, si tuviéramos que buscar 11 en la matriz anterior, tendríamos que iterar la matriz completa, pero cuando la agrupamos, limitamos nuestro alcance de iteración, mejorando así la velocidad. Esa estructura de datos utilizada para almacenar toda la información anterior se puede considerar como una matriz 2D para simplificar

Ahora, aparte del hashmap anterior, también indica que no agregará ningún duplicado en él. Y esta es la razón principal por la que tenemos que anular los iguales y el código hash

Entonces, cuando se dice que explica el funcionamiento interno de hashmap, necesitamos encontrar qué métodos tiene el hashmap y cómo sigue las reglas anteriores que expliqué anteriormente

entonces el hashmap tiene un método llamado put (K, V), y de acuerdo con el hashmap, debe seguir las reglas anteriores de distribuir eficientemente la matriz y no agregar duplicados

así que lo que pone es que primero generará el código hash para la clave dada para decidir en qué índice debe ingresar el valor. Si no hay nada presente en ese índice, entonces el nuevo valor se agregará allí, si ya hay algo presente allí entonces el nuevo valor debe agregarse después del final de la lista vinculada en ese índice. pero recuerde que no se deben agregar duplicados según el comportamiento deseado del hashmap. digamos que tienes dos objetos enteros aa = 11, bb = 11. Como cada objeto derivado de la clase de objeto, la implementación predeterminada para comparar dos objetos es que compara la referencia y no los valores dentro del objeto. Entonces, en el caso anterior, aunque sean semánticamente iguales, no pasarán la prueba de igualdad, y existe la posibilidad de que existan dos objetos que tengan el mismo código hash y los mismos valores creando así duplicados. Si anulamos, podríamos evitar agregar duplicados. También puedes referirte aDetalle de trabajo

import java.util.HashMap;


public class Employee {

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

@Override
public int hashCode() {
    System.out.println("calling hascode method of Employee");
    String str=this.name;
    Integer sum=0;
    for(int i=0;i<str.length();i++){
        sum=sum+str.charAt(i);
    }
    return sum;

}
@Override
public boolean equals(Object obj) {
    // TODO Auto-generated method stub
    System.out.println("calling equals method of Employee");
    Employee emp=(Employee)obj;
    if(this.mobile.equalsIgnoreCase(emp.mobile)){

        System.out.println("returning true");
        return true;
    }else{
        System.out.println("returning false");
        return false;
    }


}

public static void main(String[] args) {
    // TODO Auto-generated method stub

    Employee emp=new Employee("abc", "hhh");
    Employee emp2=new Employee("abc", "hhh");
    HashMap<Employee, Employee> h=new HashMap<>();
    //for (int i=0;i<5;i++){
        h.put(emp, emp);
        h.put(emp2, emp2);

    //}

    System.out.println("----------------");
    System.out.println("size of hashmap: "+h.size());


}

}
Chetan
fuente
Tengo una confusión, ¿por qué necesitamos anular el método igual cuando anulamos el método hashCode en el caso de HashMap? En cualquier caso, el hashmap reemplaza el valor si el hashcode del objeto es igual.
Vikas Verma
El hashmap de @VikasVerma no reemplaza ningún tipo de valor si el hashcode de los objetos es igual, solo decide el índice donde se debe colocar el objeto recién agregado al hashmap. Ahora puede haber objetos en el índice, por lo que para evitar duplicados, anulamos el método de igualdad y escribimos la lógica para definir cuándo los dos objetos en comparación deben tratarse como iguales. Si no se anula, los objetos que tengan los mismos valores se almacenarán porque la referencia de ambos objetos será diferente
Chetan
11

hashCode() :

Si solo anula el método de código hash, no sucederá nada. Porque siempre devuelve nuevo hashCodepara cada objeto como una clase de objeto.

equals() :

Si solo anula el método de igualdad, a.equals(b)es cierto que significa que hashCodea y b deben ser iguales pero no suceder. Porque no anulaste el hashCodemétodo.

Nota: el hashCode()método de la clase Object siempre devuelve nuevo hashCodepara cada objeto.

Entonces, cuando necesite usar su objeto en la colección basada en hashing, debe anular ambos equals()y hashCode().

Rinkal Gupta
fuente
Ese es un punto interesante, sobre anular solo hashCode () . Está totalmente bien, ¿verdad? ¿O puede haber casos problemáticos también?
Johnny
1
Esta es una respuesta engañosa e incorrecta. Anular (= solo =) hashCode () se asegura de que cada objeto que se instancia de la clase respectiva con propiedades similares tenga el mismo código hash. Pero no será útil ya que ninguno de ellos será igual entre sí.
mfaisalhyder
8

Java pone una regla que

"Si dos objetos son iguales usando el método de clase de objeto igual, entonces el método de código hash debería dar el mismo valor para estos dos objetos".

Entonces, si en nuestra clase anulamos equals(), deberíamos anular el hashcode()método también para seguir esta regla. Ambos métodos, equals()y hashcode(), se utilizan Hashtable, por ejemplo, para almacenar valores como pares clave-valor. Si anulamos uno y no el otro, existe la posibilidad de que Hashtableno funcione como deseamos, si usamos dicho objeto como clave.

Ritesh Kaushik
fuente
6

Porque si no los anula, usará la implementación predeterminada en Object.

Teniendo en cuenta que la igualdad y los valores de hascode generalmente requieren conocimiento de lo que constituye un objeto, generalmente deberán redefinirse en su clase para tener un significado tangible.

PaulJWilliams
fuente
6

Para usar nuestros propios objetos de clase como claves en colecciones como HashMap, Hashtable, etc., debemos anular ambos métodos (hashCode () y equals ()) teniendo en cuenta el funcionamiento interno de la colección. De lo contrario, conduce a resultados incorrectos que no se esperan.

Prashanth
fuente
6

Agregando a la respuesta de @Lombo

¿Cuándo necesitarás anular equals ()?

La implementación predeterminada de Object's equals () es

public boolean equals(Object obj) {
        return (this == obj);
}

lo que significa que dos objetos se considerarán iguales solo si tienen la misma dirección de memoria, lo que será cierto solo si está comparando un objeto consigo mismo.

Pero es posible que desee considerar dos objetos iguales si tienen el mismo valor para una o más de sus propiedades (Consulte el ejemplo dado en la respuesta de @Lombo).

Entonces anularás equals()en estas situaciones y darías tus propias condiciones para la igualdad.

He implementado con éxito equals () y está funcionando muy bien. Entonces, ¿por qué piden anular también hashCode ()?

Bueno, siempre y cuando no uses Colecciones basadas en "Hash" en tu clase definida por el usuario, está bien. Pero en el futuro es posible que desee usar HashMapo HashSetsi no lo hace overridey "implementa correctamente" hashCode () , esta colección basada en Hash no funcionará según lo previsto.

Reemplazar solo es igual (adición a la respuesta de @Lombo)

myMap.put(first,someValue)
myMap.contains(second); --> But it should be the same since the key are the same.But returns false!!! How?

En primer lugar, HashMap comprueba si el hashCode de secondes el mismo quefirst . Solo si los valores son los mismos, se procederá a verificar la igualdad en el mismo depósito.

Pero aquí el hashCode es diferente para estos 2 objetos (porque tienen una dirección de memoria diferente de la implementación predeterminada). Por lo tanto, ni siquiera le importará verificar la igualdad.

Si tiene un punto de interrupción dentro de su método equals () anulado, no intervendría si tienen diferentes códigos hash. contains()comprueba hashCode()y solo si son iguales llamaría a su equals()método.

¿Por qué no podemos hacer que HashMap verifique la igualdad en todos los cubos? ¡¡Entonces no hay necesidad de anular hashCode () !!

Entonces te estás perdiendo el punto de las colecciones basadas en hash. Considera lo siguiente :

Your hashCode() implementation : intObject%9.

Las siguientes son las claves almacenadas en forma de cubos.

Bucket 1 : 1,10,19,... (in thousands)
Bucket 2 : 2,20,29...
Bucket 3 : 3,21,30,...
...

Digamos que quieres saber si el mapa contiene la clave 10. ¿Quieres buscar en todos los cubos? o ¿Desea buscar solo un cubo?

Basado en el hashCode, usted identificaría que si 10 está presente, debe estar presente en el Bucket 1. ¡Entonces solo se buscará el Bucket 1!

usuario104309
fuente
5
class A {
    int i;
    // Hashing Algorithm
    if even number return 0 else return 1
    // Equals Algorithm,
    if i = this.i return true else false
}
  • put ('clave', 'valor') calculará el valor hash usando hashCode()para determinar el depósito y usa el equals()método para encontrar si el valor ya está presente en el depósito. Si no se agregará más, se reemplazará con el valor actual
  • get ('key') se usará hashCode()para buscar primero la entrada (bucket) y equals()para encontrar el valor en Entry

si ambos se anulan,

Mapa < A >

Map.Entry 1 --> 1,3,5,...
Map.Entry 2 --> 2,4,6,...

si igual no se anula

Mapa < A >

Map.Entry 1 --> 1,3,5,...,1,3,5,... // Duplicate values as equals not overridden
Map.Entry 2 --> 2,4,6,...,2,4,..

Si hashCode no se anula

Mapa < A >

Map.Entry 1 --> 1
Map.Entry 2 --> 2
Map.Entry 3 --> 3
Map.Entry 4 --> 1
Map.Entry 5 --> 2
Map.Entry 6 --> 3 // Same values are Stored in different hasCodes violates Contract 1
So on...

HashCode Equal Contract

  1. Dos claves iguales según el mismo método deberían generar el mismo código hash
  2. Dos claves que generan el mismo código hash no necesitan ser iguales (en el ejemplo anterior, todos los números pares generan el mismo código hash)
bharanitharan
fuente
4

Considere la recolección de bolas en un cubo todo en color negro. Su trabajo es colorear esas bolas de la siguiente manera y usarlas para el juego apropiado,

Para tenis: amarillo, rojo. Para Cricket - Blanco

Ahora el cubo tiene bolas en tres colores amarillo, rojo y blanco. Y eso ahora hiciste el color Solo tú sabes qué color es para qué juego.

Colorear las bolas - Hashing. Elegir la pelota para el juego - Igual.

Si hiciste el color y alguien elige la pelota para el cricket o el tenis, ¡no les importará el color!

bharanitharan
fuente
4

Estaba buscando la explicación "Si solo anula el código hash, entonces cuando lo llama myMap.put(first,someValue)toma primero, calcula su código hash y lo almacena en un cubo determinado. Luego, cuando llamamyMap.put(first,someOtherValue) , debe reemplazar primero por segundo según la Documentación del mapa porque son iguales (según nuestra definición) ". :

Creo que la segunda vez que estamos agregando myMap, debería ser el 'segundo' objeto comomyMap.put(second,someOtherValue)

Narrador
fuente
4

1) El error común se muestra en el siguiente ejemplo.

public class Car {

    private String color;

    public Car(String color) {
        this.color = color;
    }

    public boolean equals(Object obj) {
        if(obj==null) return false;
        if (!(obj instanceof Car))
            return false;   
        if (obj == this)
            return true;
        return this.color.equals(((Car) obj).color);
    }

    public static void main(String[] args) {
        Car a1 = new Car("green");
        Car a2 = new Car("red");

        //hashMap stores Car type and its quantity
        HashMap<Car, Integer> m = new HashMap<Car, Integer>();
        m.put(a1, 10);
        m.put(a2, 20);
        System.out.println(m.get(new Car("green")));
    }
}

el coche verde no se encuentra

2. Problema causado por hashCode ()

El problema es causado por el método no reemplazado hashCode(). El contrato entre equals()y hashCode()es:

  1. Si dos objetos son iguales, entonces deben tener el mismo código hash.
  2. Si dos objetos tienen el mismo código hash, pueden o no ser iguales.

    public int hashCode(){  
      return this.color.hashCode(); 
    }
Neeraj Gahlawat
fuente
4

Es útil cuando se utilizan objetos de valor . Lo siguiente es un extracto del repositorio de patrones de Portland :

Ejemplos de objetos de valor son cosas como números, fechas, dineros y cadenas. Por lo general, son objetos pequeños que se usan bastante ampliamente. Su identidad se basa en su estado más que en su identidad de objeto. De esta manera, puede tener múltiples copias del mismo objeto de valor conceptual.

Por lo tanto, puedo tener varias copias de un objeto que represente la fecha 16 de enero de 1998. Cualquiera de estas copias será igual entre sí. Para un objeto pequeño como este, a menudo es más fácil crear nuevos y moverlos en lugar de confiar en un solo objeto para representar la fecha.

Un objeto de valor siempre debe anular .equals () en Java (o = en Smalltalk). (Recuerde anular .hashCode () también).

Ionuț G. Stan
fuente
3

Suponga que tiene la clase (A) que agrega otros dos (B) (C), y necesita almacenar instancias de (A) dentro de la tabla hash. La implementación predeterminada solo permite distinguir instancias, pero no por (B) y (C). Entonces, dos instancias de A podrían ser iguales, pero el valor predeterminado no le permitiría compararlas de la manera correcta.

Rocío
fuente
3

Los métodos equals y hashcode se definen en la clase de objeto. Por defecto, si el método igual devuelve verdadero, el sistema irá más allá y verificará el valor del código hash. Si el código hash de los 2 objetos también es el mismo, los objetos se considerarán iguales. Entonces, si anula solo el método igual, entonces, aunque el método igual anulado indica que 2 objetos son iguales, el código hash definido por el sistema puede no indicar que los 2 objetos son iguales. Por lo tanto, también debemos anular el código hash.

Aarti
fuente
Si el método igual devuelve verdadero, no hay necesidad de verificar el código hash. Sin embargo, si dos objetos tienen códigos hash diferentes, uno debería poder considerarlos diferentes sin tener que llamar a iguales. Además, saber que ninguna de las cosas en una lista tiene un código hash particular implica que ninguna de las cosas en la lista puede coincidir con ningún objeto con ese código hash. Como ejemplo simple, si uno tiene una lista de objetos cuyos códigos hash son números pares, y una lista de objetos donde son números impares, ningún objeto cuyo código hash sea un número par estará en la segunda lista.
supercat
Si uno tenía dos objetos X e Y cuyos métodos "iguales" indicaban que coincidían, pero el código hash de X era un número par y el código hash de Y era un número impar, una colección como se describió anteriormente que señaló que el código hash del objeto Y era impar y almacenado en la segunda lista no podría encontrar una coincidencia para el objeto X. Observaría que el código hash de X era par, y dado que la segunda lista no tiene ningún objeto con códigos hash pares, no molestaría buscar allí algo que coincida con X, a pesar de que Y coincidiría con X. Lo que deberías decir ...
supercat
... sería que muchas colecciones evitarán comparar cosas cuyos códigos hash implicarían que no pueden ser iguales. Dados dos objetos cuyos códigos hash son desconocidos, a menudo es más rápido para compararlos directamente que compute sus códigos hash, por lo que no hay garantía de que las cosas que informan códigos hash desiguales pero regreso truepara equalsno serán considerados como juego. Por otro lado, si las colecciones se dan cuenta de que las cosas no pueden tener el mismo código hash, es probable que no se den cuenta de que son iguales.
supercat
3

Métodos de igualdad y hashcode en Java

Son métodos de la clase java.lang.Object, que es la superclase de todas las clases (clases personalizadas y otras definidas en la API de Java).

Implementación:

public boolean equals (Objeto obj)

public int hashCode ()

ingrese la descripción de la imagen aquí

public boolean equals (Objeto obj)

Este método simplemente verifica si dos referencias de objeto x e y se refieren al mismo objeto. es decir, comprueba si x == y.

Es reflexivo: para cualquier valor de referencia x, x.equals (x) debería devolver verdadero.

Es simétrico: para cualquier valor de referencia x e y, x.equals (y) debería devolver verdadero si y solo si y.equals (x) devuelve verdadero.

Es transitivo: para cualquier valor de referencia x, y y z, si x.equals (y) devuelve verdadero e y.equals (z) devuelve verdadero, entonces x.equals (z) debería devolver verdadero.

Es coherente: para cualquier valor de referencia x e y, las invocaciones múltiples de x.equals (y) devuelven constantemente verdadero o constantemente devuelven falso, siempre que no se modifique la información utilizada en comparaciones iguales en el objeto.

Para cualquier valor de referencia no nulo x, x.equals (nulo) debería devolver falso.

public int hashCode ()

Este método devuelve el valor del código hash para el objeto en el que se invoca este método. Este método devuelve el valor del código hash como un entero y es compatible con el beneficio de clases de colección basadas en hash como Hashtable, HashMap, HashSet, etc. Este método debe ser anulado en cada clase que anula el método de igual.

El contrato general de hashCode es:

Cada vez que se invoca en el mismo objeto más de una vez durante la ejecución de una aplicación Java, el método hashCode debe devolver constantemente el mismo entero, siempre que no se modifique la información utilizada en comparaciones iguales sobre el objeto.

Este número entero no necesita permanecer consistente de una ejecución de una aplicación a otra ejecución de la misma aplicación.

Si dos objetos son iguales de acuerdo con el método igual (Objeto), entonces llamar al método hashCode en cada uno de los dos objetos debe producir el mismo resultado entero.

No se requiere que si dos objetos son desiguales de acuerdo con el método igual (java.lang.Object), llamar al método hashCode en cada uno de los dos objetos debe producir resultados enteros distintos. Sin embargo, el programador debe ser consciente de que producir resultados enteros distintos para objetos desiguales puede mejorar el rendimiento de las tablas hash.

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.

Recursos:

JavaRanch

Imagen

Affy
fuente
La imagen (enlace de video) está en modo privado. Hazlo público para mirar.
UdayKiran Pulipati
2

En el siguiente ejemplo, si comenta la anulación para equals o hashcode en la clase Person, este código no podrá buscar el orden de Tom. El uso de la implementación predeterminada de hashcode puede causar fallas en las búsquedas de tablas hash.

Lo que tengo a continuación es un código simplificado que extrae el orden de las personas por persona. La persona se está utilizando como clave en la tabla hash.

public class Person {
    String name;
    int age;
    String socialSecurityNumber;

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

    @Override
    public boolean equals(Object p) {
        //Person is same if social security number is same

        if ((p instanceof Person) && this.socialSecurityNumber.equals(((Person) p).socialSecurityNumber)) {
            return true;
        } else {
            return false;
        }

    }

    @Override
    public int hashCode() {        //I am using a hashing function in String.java instead of writing my own.
        return socialSecurityNumber.hashCode();
    }
}


public class Order {
    String[]  items;

    public void insertOrder(String[]  items)
    {
        this.items=items;
    }

}



import java.util.Hashtable;

public class Main {

    public static void main(String[] args) {

       Person p1=new Person("Tom",32,"548-56-4412");
        Person p2=new Person("Jerry",60,"456-74-4125");
        Person p3=new Person("Sherry",38,"418-55-1235");

        Order order1=new Order();
        order1.insertOrder(new String[]{"mouse","car charger"});

        Order order2=new Order();
        order2.insertOrder(new String[]{"Multi vitamin"});

        Order order3=new Order();
        order3.insertOrder(new String[]{"handbag", "iPod"});

        Hashtable<Person,Order> hashtable=new Hashtable<Person,Order>();
        hashtable.put(p1,order1);
        hashtable.put(p2,order2);
        hashtable.put(p3,order3);

       //The line below will fail if Person class does not override hashCode()
       Order tomOrder= hashtable.get(new Person("Tom", 32, "548-56-4412"));
        for(String item:tomOrder.items)
        {
            System.out.println(item);
        }
    }
}
desarrollador747
fuente
2

La clase de cadena y las clases de contenedor tienen una implementación diferente de equals()yhashCode() métodos que la clase de objeto. El método equals () de la clase Object compara las referencias de los objetos, no los contenidos. El método hashCode () de la clase Object devuelve un código hash distinto para cada objeto, independientemente de si el contenido es el mismo.

Conduce un problema cuando usa la colección de mapas y la clave es de tipo Persistente, tipo StringBuffer / builder. Como no anulan equals () y hashCode () a diferencia de la clase String, equals () devolverá falso cuando compare dos objetos diferentes, aunque ambos tengan el mismo contenido. Hará que el hashMap almacene las mismas claves de contenido. Almacenar las mismas claves de contenido significa que está violando la regla de Map porque Map no permite duplicar claves en absoluto. Por lo tanto, anula los métodos equals () y hashCode () en su clase y proporciona la implementación (IDE puede generar estos métodos) para que funcionen igual que los equals () y hashCode () de String y eviten las mismas claves de contenido.

Debe anular el método hashCode () junto con equals () porque equals () funciona según el código hash.

Además, anular el método hashCode () junto con equals () ayuda a mantener intacto el contrato equals () - hashCode (): "Si dos objetos son iguales, entonces deben tener el mismo código hash".

¿Cuándo necesita escribir una implementación personalizada para hashCode ()?

Como sabemos, el funcionamiento interno de HashMap se basa en el principio de Hashing. Hay ciertos cubos donde se almacenan los conjuntos de entradas. Puede personalizar la implementación de hashCode () según sus requisitos para que los mismos objetos de categoría se puedan almacenar en el mismo índice. Cuando almacena los valores en la colección de mapas utilizando el put(k,v)método, la implementación interna de put () es:

put(k, v){
hash(k);
index=hash & (n-1);
}

Significa, genera índice y el índice se genera en base al código hash de un objeto clave particular. Por lo tanto, haga que este método genere código hash de acuerdo con sus requisitos porque los mismos conjuntos de entradas de código hash se almacenarán en el mismo depósito o índice.

¡Eso es!

Arun Raaj
fuente
1

hashCode()El método se utiliza para obtener un número entero único para un objeto dado. Este entero se usa para determinar la ubicación del depósito, cuando este objeto necesita ser almacenado en algunos HashTable, HashMapcomo la estructura de datos. Por defecto, el hashCode()método Object devuelve una representación entera de la dirección de memoria donde se almacena el objeto.

El hashCode()método de los objetos se usa cuando los insertamos en a HashTable, HashMapo HashSet. Más información HashTablesen Wikipedia.org para referencia.

Para insertar cualquier entrada en la estructura de datos del mapa, necesitamos clave y valor. Si tanto la clave como los valores son tipos de datos definidos por el usuario, hashCode()la clave determinará dónde almacenar el objeto internamente. Cuando sea necesario buscar el objeto del mapa también, el código hash de la clave determinará dónde buscar el objeto.

El código hash solo apunta internamente a una determinada "área" (o lista, depósito, etc.). Dado que diferentes objetos clave podrían tener el mismo código hash, el código hash en sí mismo no garantiza que se encuentre la clave correcta. La HashTablecontinuación itera esta área (todas las claves con el mismo código hash) y utiliza la clave del equals()método para encontrar la clave correcta. Una vez que se encuentra la clave correcta, se devuelve el objeto almacenado para esa clave.

Entonces, como podemos ver, se utiliza una combinación de los métodos hashCode()y equals()al almacenar y al buscar objetos en a HashTable.

NOTAS

  1. Utilice siempre los mismos atributos de un objeto para generar hashCode()y equals()ambos. Como en nuestro caso, hemos utilizado la identificación del empleado.

  2. equals() debe ser coherente (si los objetos no se modifican, debe seguir devolviendo el mismo valor).

  3. Cada vez a.equals(b), entonces a.hashCode()debe ser igual que b.hashCode().

  4. Si anula uno, entonces debe anular el otro.

http://parameshk.blogspot.in/2014/10/examples-of-comparable-comporator.html

Paramesh Korrakuti
fuente
hashCode()no se utiliza para devolver un número entero único para cada objeto. Eso es imposible. Usted mismo ha contradicho esto en la segunda oración del cuarto párrafo.
Marqués de Lorne
@EJP, la mayoría de las veces hascode () devolverá el entero único para dos objetos diferentes. Pero habrá posibilidades de colisionar hascode para dos objetos diferentes, este concepto se llama Colisión Hashcode . Consulte: tech.queryhome.com/96931/…
Paramesh Korrakuti
1

En mi humilde opinión, es según la regla dice: si dos objetos son iguales, entonces deberían tener el mismo hash, es decir, los objetos iguales deberían producir valores de hash iguales.

Dado anteriormente, el valor predeterminado equals () en Object es == que hace una comparación en la dirección, hashCode () devuelve la dirección en entero (hash en la dirección real) que nuevamente es distinta para Object distinto.

Si necesita usar los Objetos personalizados en las colecciones basadas en Hash, debe anular tanto equals () como hashCode (), por ejemplo, si deseo mantener el HashSet de los Objetos de empleado, si no utilizo hashCode y equals más fuertes Puedo terminar anulando los dos Objetos de empleado diferentes, esto sucede cuando uso la edad como hashCode (), sin embargo, debería estar usando el valor único que puede ser la ID de empleado.

Cleonjoys
fuente
1

Para ayudarlo a verificar si hay objetos duplicados, necesitamos un igual y un código hash personalizado.

Dado que el código hash siempre devuelve un número, siempre es rápido recuperar un objeto utilizando un número en lugar de una clave alfabética. ¿Cómo va a hacer? Supongamos que creamos un nuevo objeto al pasar algún valor que ya está disponible en algún otro objeto. Ahora el nuevo objeto devolverá el mismo valor hash que otro objeto porque el valor pasado es el mismo. Una vez que se devuelve el mismo valor hash, JVM irá a la misma dirección de memoria cada vez y, en caso de que haya más de un objeto presente para el mismo valor hash, utilizará el método equals () para identificar el objeto correcto.

Tavash
fuente
1

Cuando desee almacenar y recuperar su objeto personalizado como clave en Map, siempre debe anular equals y hashCode en su objeto personalizado. P.ej:

Person p1 = new Person("A",23);
Person p2 = new Person("A",23);
HashMap map = new HashMap();
map.put(p1,"value 1");
map.put(p2,"value 2");

Aquí p1 y p2 se considerarán como un solo objeto y el maptamaño será solo 1 porque son iguales.

Rajput ambrish
fuente
1
public class Employee {

    private int empId;
    private String empName;

    public Employee(int empId, String empName) {
        super();
        this.empId = empId;
        this.empName = empName;
    }

    public int getEmpId() {
        return empId;
    }

    public void setEmpId(int empId) {
        this.empId = empId;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName;
    }

    @Override
    public String toString() {
        return "Employee [empId=" + empId + ", empName=" + empName + "]";
    }

    @Override
    public int hashCode() {
        return empId + empName.hashCode();
    }

    @Override
    public boolean equals(Object obj) {

        if (this == obj) {
            return true;
        }
        if (!(this instanceof Employee)) {
            return false;
        }
        Employee emp = (Employee) obj;
        return this.getEmpId() == emp.getEmpId() && this.getEmpName().equals(emp.getEmpName());
    }

}

Clase de prueba

public class Test {

    public static void main(String[] args) {
        Employee emp1 = new Employee(101,"Manash");
        Employee emp2 = new Employee(101,"Manash");
        Employee emp3 = new Employee(103,"Ranjan");
        System.out.println(emp1.hashCode());
        System.out.println(emp2.hashCode());
        System.out.println(emp1.equals(emp2));
        System.out.println(emp1.equals(emp3));
    }

}

En Object Class equals (Object obj) se usa para comparar la comparación de direcciones, por eso cuando en Test class si compara dos objetos, entonces es igual al método que da falso pero cuando anulamos hashcode () puede comparar el contenido y dar el resultado adecuado.

Manash Ranjan Dakua
fuente
y clase de prueba que agregué en el siguiente programa.
Manash Ranjan Dakua
En Object Class equals (Object obj) se usa para comparar la comparación de direcciones, es por eso que en Test class si compara dos objetos, entonces es igual al método que da falso, pero cuando anulamos hashcode (), puede comparar el contenido y dar el resultado adecuado.
Manash Ranjan Dakua
1
puede usar el enlace de edición justo debajo de esta respuesta para agregar a su respuesta. Por favor, no agregue una respuesta como dos incompletas
Suraj Rao
1

Si anula equals()y no hashcode(), no encontrará ningún problema a menos que usted u otra persona use ese tipo de clase en una colección hash como HashSet. La gente antes que yo ha explicado claramente la teoría documentada varias veces, solo estoy aquí para proporcionar un ejemplo muy simple.

Considere una clase cuya equals()necesidad de significar algo personalizado:

    public class Rishav {

        private String rshv;

        public Rishav(String rshv) {
            this.rshv = rshv;
        }

        /**
        * @return the rshv
        */
        public String getRshv() {
            return rshv;
        }

        /**
        * @param rshv the rshv to set
        */
        public void setRshv(String rshv) {
            this.rshv = rshv;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Rishav) {
                obj = (Rishav) obj;
                if (this.rshv.equals(((Rishav) obj).getRshv())) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }

        @Override
        public int hashCode() {
            return rshv.hashCode();
        }

    }

Ahora considere esta clase principal: -

    import java.util.HashSet;
    import java.util.Set;

    public class TestRishav {

        public static void main(String[] args) {
            Rishav rA = new Rishav("rishav");
            Rishav rB = new Rishav("rishav");
            System.out.println(rA.equals(rB));
            System.out.println("-----------------------------------");

            Set<Rishav> hashed = new HashSet<>();
            hashed.add(rA);
            System.out.println(hashed.contains(rB));
            System.out.println("-----------------------------------");

            hashed.add(rB);
            System.out.println(hashed.size());
        }

    }

Esto producirá el siguiente resultado: -

    true
    -----------------------------------
    true
    -----------------------------------
    1

Estoy contento con los resultados. Pero si no lo he anulado hashCode(), causará pesadilla ya que los objetos Rishavcon el mismo contenido de miembro ya no serán tratados como únicos, ya hashCodeque serán diferentes, como se genera por el comportamiento predeterminado, aquí está el resultado:

    true
    -----------------------------------
    false
    -----------------------------------
    2
91StarSky
fuente
0

Ambos métodos están definidos en la clase Object. Y ambos están en su implementación más simple. Entonces, cuando lo necesite, desea agregar más implementación a estos métodos, entonces tiene una anulación en su clase.

Por ejemplo: el método equals () en el objeto solo verifica su igualdad en la referencia. Entonces, si necesita comparar su estado también, puede anularlo como se hace en la clase String.

GuruKulki
fuente
-3

Bah: "Debe anular hashCode () en cada clase que anule equals ()".

[de Java efectivo, por Joshua Bloch?]

¿No es este el camino equivocado? Anular hashCode probablemente implica que está escribiendo una clase de clave hash, pero anular igual ciertamente no lo hace. Hay muchas clases que no se usan como claves hash, pero quieren un método de prueba de igualdad lógica por alguna otra razón. Si elige "igual" para él, entonces se le puede ordenar que escriba una implementación de hashCode mediante la aplicación excesiva de esta regla. Todo lo que logra es agregar código no probado en la base de código, un mal que espera hacer tropezar a alguien en el futuro. También escribir código que no necesita es anti-ágil. Simplemente está mal (y una idea generada probablemente sea incompatible con tus iguales hechos a mano).

¿Seguramente deberían haber ordenado una interfaz en los objetos escritos para ser utilizados como claves? En cualquier caso, Object nunca debería haber proporcionado hashCode () y equals () imho por defecto. Probablemente se alienta a muchas colecciones hash rotas.

Pero de todos modos, creo que la "regla" está escrita al frente. Mientras tanto, seguiré evitando el uso de "iguales" para los métodos de prueba de igualdad :-(

Alan Dobbie
fuente