¿Por qué anular el método equals (Object) y hashCode()?

Requisito previo: el método Equals y Hashcode
HashMap y HashSet usan el valor de código hash de un objeto para averiguar cómo se almacenaría el objeto en la colección y, posteriormente, se usa el código hash para ayudar a localizar el objeto en la colección. La recuperación de hash implica:

  1. Primero, encuentre el depósito correcto usando hashCode().
  2. En segundo lugar, busque en el cubo el elemento correcto usando equals()

Consideremos todos los casos de Overriding en estos métodos

Caso 1: Anular los métodos equals(Object) y hashCode()

Debe anular hashCode() en todas las clases que anulan equals(). Si no lo hace, se violará el 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. (-Joshua Bloch)
Aquí está el contrato, de la especialización java.lang.Object:

  • Siempre que se invoque it(hashcode) en el mismo objeto más de una vez durante la ejecución de una aplicación Java, el método hashCode debe devolver de forma consistente el mismo entero, siempre que no se modifique la información utilizada en las comparaciones de igualdad en el objeto. Este 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 según el método equals(Object), entonces llamar al método hashCode en cada uno de los dos objetos debe producir el mismo resultado entero.
  • No es necesario que si dos objetos no son iguales según el método equals(java.lang.Object), llamar al método hashCode en cada uno de los dos objetos deba producir resultados enteros distintos. Sin embargo, el programador debe tener en cuenta que la producción de resultados enteros distintos para objetos desiguales puede mejorar el rendimiento de las tablas hash.
// Java program to illustrate
// overriding of equals and
// hashcode methods
import java.io.*;
import java.util.*;
  
class Geek 
{
      
    String name;
    int id;
      
    Geek(String name, int id)
    {
          
        this.name = name;
        this.id = id;
    }
      
    @Override
    public boolean equals(Object obj)
    {
          
    // if both the object references are 
    // referring to the same object.
    if(this == obj)
            return true;
          
        // it checks if the argument is of the 
        // type Geek by comparing the classes 
        // of the passed argument and this object.
        // if(!(obj instanceof Geek)) return false; ---> avoid.
        if(obj == null || obj.getClass()!= this.getClass())
            return false;
          
        // type casting of the argument. 
        Geek geek = (Geek) obj;
          
        // comparing the state of argument with 
        // the state of 'this' Object.
        return (geek.name.equals(this.name)  && geek.id == this.id);
    }
      
    @Override
    public int hashCode()
    {
          
        // We are returning the Geek_id 
        // as a hashcode value.
        // we can also return some 
        // other calculated value or may
        // be memory address of the 
        // Object on which it is invoked. 
        // it depends on how you implement 
        // hashCode() method.
        return this.id;
    }
      
}
  
// Driver code
class GFG 
{
    public static void main (String[] args) 
    {
          
        // creating two Objects with 
        // same state
        Geek g1 = new Geek("aditya", 1);
        Geek g2 = new Geek("aditya", 1);
          
        Map<Geek, String> map = new HashMap<Geek, String>();
        map.put(g1, "CSE");
        map.put(g2, "IT");
          
        for(Geek geek : map.keySet())
        {
            System.out.println(map.get(geek).toString());
        }
  
    }
}

producción:

IT

En este caso, anulamos ambos métodos correctamente.
Cuando llamamos map.put(g1, “CSE”); hará hash a alguna ubicación de depósito y cuando llamemos a map.put(g2, «IT»); , generará el mismo valor de código hash (igual que g1) y reemplazará el primer valor por el segundo valor porque, al iterar sobre el mismo depósito, encontró una k tal que k.equals (g2) es verdadera, significa que la clave de búsqueda ya existe. Entonces, reemplaza el valor anterior de esa clave por un valor nuevo.

Caso 2: Anulando solo el método equals(Object)

Si solo anulamos el método equals(Object), cuando llamamos a map.put(g1, “CSE”); hará hash a alguna ubicación de depósito y cuando llamemos a map.put(g2, «IT»); se convertirá en hash en alguna otra ubicación del depósito debido a un valor de código hash diferente, ya que el método hashCode() no se ha anulado. Como puede ver claramente en la imagen, ambos valores se almacenan en diferentes ubicaciones de depósitos. Así, cada inserción en el mapa obtendrá una ubicación de cubo diferente, ya sea que estemos usando los mismos objetos clave o diferentes, es decir, el estado de los objetos clave es el mismo o diferente.
hashcoe_1

// Java program to illustrate
// Overriding only the equals(Object) method
import java.io.*;
import java.util.*;
  
class Geek 
{
    String name;
    int id;
       
    Geek(String name, int id)
    {
        this.name = name;
        this.id = id;
     }
       
    @Override
    public boolean equals(Object obj)
    {
       // if both the object references are 
       // referring to the same object.
       if(this == obj)
            return true;
            
        // it checks if the argument is of the 
        // type Geek by comparing the classes 
        // of the passed argument and this object.
        // if(!(obj instanceof Geek)) return false; ---> avoid.
        if(obj == null || obj.getClass()!= this.getClass())
            return false;
            
        // type casting of the argument.    
        Geek geek = (Geek) obj;
            
        // comparing the state of argument with 
        // the state of 'this' Object.
        return (geek.name.equals(this.name) && geek.id == this.id);
    }
}
  
class GFG 
{
    public static void main (String[] args) 
    {
          
        // creating two Objects with 
        // same state
        Geek g1 = new Geek("aditya", 1);
        Geek g2 = new Geek("aditya", 1);
          
        Map<Geek, String> map = new HashMap<Geek, String>();
        map.put(g1, "CSE");
        map.put(g2, "IT");
          
        for(Geek geek : map.keySet())
        {
            System.out.println(map.get(geek).toString());
        }
  
    }
}

Producción:

CSE
IT

Caso 3: anulando solo el método hashCode()

Considere otro ejemplo de mapa:

Map map = new HashMap();
map.put(“xyz”, “CSE”);
map.put(“xyz”, “IT”);

Cuando llamamos a map.put(“xyz”, “CSE”); generará un valor de código hash y lo almacenará en la ubicación del depósito que se especifica con esta dirección (valor de código hash). Y cuando llamamos a map.put(“xyz”, “IT”); genera el mismo valor de código hash que la entrada anterior, ya que los objetos clave son los mismos y el método hashCode() se anuló. Por lo tanto, debe reemplazar primero con segundo según la regla. Pero no fue así. La razón es, cuando itera a través de ese cubo y busca encontrar k tal que k.equals («xyz»), es decir, si la clave de búsqueda ya existe. Pero no se encuentra porque el método equals(Object) no ha sido anulado. Es una violación de la regla de hashing.

// Java program to illustrate 
// Overriding only hashCode() method
  
import java.io.*;
import java.util.*;
  
class Geek 
{
    String name;
    int id;
       
    Geek(String name, int id)
    {
        this.name = name;
        this.id = id;
     }
      
    @Override
    public int hashCode()
    {
           
        // We are returning the Geek_id 
        // as a hashcode value.
        // we can also return some 
        // other calculated value or may
        // be memory address of the 
        // Object on which it is invoked. 
        // it depends on how you implement 
        // hashCode() method.
        return this.id;
    }
       
}
  
class GFG 
{
    public static void main (String[] args)
    {
          
        // creating two Objects with 
        // same state
        Geek g1 = new Geek("aditya", 1);
        Geek g2 = new Geek("aditya", 1);
          
        Map<Geek, String> map = new HashMap<Geek, String>();
        map.put(g1, "CSE");
        map.put(g2, "IT");
          
        for(Geek geek : map.keySet())
        {
            System.out.println(map.get(geek).toString());
        }
  
    }
}

Producción:

CSE
IT

hashcode_3

En el diagrama anterior, cuando llamamos map.put («xyz», «IT»); luego trató de reemplazar el primer valor (CSE) por el segundo valor (IT), pero no fue posible, por lo que insertó el segundo par (clave, valor) en un nuevo Node LinkedList que el hashmap usa internamente. Es una violación total de la regla ya que la clave es única en el mapa.

Referencia: StackOverflow

Este artículo es una contribución de Nitsdheerendra . Si le gusta GeeksforGeeks y le gustaría contribuir, también puede escribir un artículo usando contribuya.geeksforgeeks.org o envíe su artículo por correo a contribuya@geeksforgeeks.org. Vea su artículo que aparece en la página principal de GeeksforGeeks y ayude a otros Geeks.

Escriba comentarios si encuentra algo incorrecto o si desea compartir más información sobre el tema tratado anteriormente.

Publicación traducida automáticamente

Artículo escrito por GeeksforGeeks-1 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *