Java equals() y hashCode()

Los métodos equals() y hashCode() de Java están presentes en la clase Object. Por lo tanto, cada clase Java obtiene la implementación predeterminada de equals() y hashCode(). En esta publicación, analizaremos en detalle los métodos equals() y hashCode() de Java.

Java es igual a()

La clase de objeto define el método equals() de la siguiente manera:

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

Según la documentación de Java del método equals(), cualquier implementación debe cumplir con los siguientes principios.

  • Para cualquier objeto x, x.equals(x)debe devolver true.
  • Para cualesquiera dos objetos x e y, x.equals(y)debe retornar truesi y solo si y.equals(x)devuelve true.
  • Para varios objetos x, y, z, si x.equals(y)devuelve truey y.equals(z)devuelve true, entonces x.equals(z)debería devolver true.
  • Varias invocaciones de x.equals(y)deben devolver el mismo resultado, a menos que se modifique alguna de las propiedades del objeto que se utiliza en la equals()implementación del método.
  • La implementación del método equals() de la clase de objeto retorna truesolo cuando ambas referencias apuntan al mismo objeto.

Código hash de Java()

El método hashCode() de Java Object es un método nativo que devuelve el valor del código hash entero del objeto. El contrato general del método hashCode() es:

  • Varias invocaciones de hashCode() deben devolver el mismo valor entero, a menos que se modifique la propiedad del objeto que se está utilizando en el método equals().
  • Un valor de código hash de un objeto puede cambiar en múltiples ejecuciones de la misma aplicación.
  • Si dos objetos son iguales según el método equals(), entonces su código hash debe ser el mismo.
  • Si dos objetos no son iguales según el método equals(), no es necesario que sus códigos hash sean diferentes. El valor de sus códigos hash puede ser igual o no.

Importancia de los métodos equals() y hashCode()

Los métodos hashCode() y equals() de Java se utilizan en implementaciones basadas en tablas hash en Java para almacenar y recuperar datos. Lo he explicado en detalle en ¿Cómo funciona HashMap en Java? La implementación de equals() y hashCode() debe seguir estas reglas.

  • Si o1.equals(o2), entonces o1.hashCode() == o2.hashCode()siempre debería ser true.
  • Si o1.hashCode() == o2.hashCodees cierto, no significa que lo o1.equals(o2)será true.

¿Cuándo anular los métodos equals() y hashCode()?

Cuando reemplazamos el método equals(), es casi necesario reemplazar también el método hashCode() para que nuestra implementación no viole su contrato. Tenga en cuenta que su programa no generará ninguna excepción si se viola el contrato equals() y hashCode(), si no planea usar la clase como clave de la tabla Hash, entonces no creará ningún problema. Si planea usar una clase como clave de la tabla Hash, entonces es necesario reemplazar los métodos equals() y hashCode(). Veamos qué sucede cuando confiamos en la implementación predeterminada de los métodos equals() y hashCode() y usamos una clase personalizada como clave HashMap.

package com.journaldev.java;public class DataKey {private String name;private int id;        // getter and setter methods@Overridepublic String toString() {return "DataKey [name=" + name + ",]";}}
package com.journaldev.java;import java.util.HashMap;import java.util.Map;public class HashingTest {public static void main(String[] args) {MapDataKey, Integer hm = getAllData();DataKey dk = new DataKey();dk.setId(1);dk.setName("Pankaj");System.out.println(dk.hashCode());Integer value = hm.get(dk);System.out.println(value);}private static MapDataKey, Integer getAllData() {MapDataKey, Integer hm = new HashMap();DataKey dk = new DataKey();dk.setId(1);dk.setName("Pankaj");System.out.println(dk.hashCode());hm.put(dk, 10);return hm;}}

Cuando ejecutamos el programa anterior, se imprimirá null. Esto se debe a que se utiliza el método Object hashCode() para encontrar el depósito en el que buscar la clave. Como no tenemos acceso a las claves de HashMap y estamos creando la clave nuevamente para recuperar los datos, notará que los valores del código hash de ambos objetos son diferentes y, por lo tanto, no se encuentra el valor.

Implementación de los métodos equals() y hashCode()

Podemos definir nuestra propia implementación de los métodos equals() y hashCode() pero si no los implementamos con cuidado, pueden surgir problemas extraños en tiempo de ejecución. Afortunadamente, la mayoría de los IDE actuales ofrecen formas de implementarlos automáticamente y, si es necesario, podemos cambiarlos según nuestros requisitos. Podemos usar Eclipse para generar automáticamente los métodos equals() y hashCode(). Aquí se muestran las implementaciones de los métodos equals() y hashCode() generados automáticamente.

@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + id;result = prime * result + ((name == null) ? 0 : name.hashCode());return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;DataKey other = (DataKey) obj;if (id != other.id)return false;if (name == null) {if (other.name != null)return false;} else if (!name.equals(other.name))return false;return true;}

Tenga en cuenta que los métodos equals() y hashCode() utilizan los mismos campos para los cálculos, por lo que su contrato sigue siendo válido. Si vuelve a ejecutar el programa de prueba, obtendremos el objeto del mapa y el programa imprimirá 10. También podemos utilizar Project Lombok para generar automáticamente las implementaciones de los métodos equals y hashCode.

¿Qué es la colisión hash?

En términos muy simples, las implementaciones de tablas hash de Java utilizan la siguiente lógica para las operaciones de obtención y colocación.

  1. Primero identifique el “Bucket” a utilizar usando el código hash “clave”.
  2. Si no hay objetos presentes en el depósito con el mismo código hash, agregue el objeto para la operación de colocación y devuelva nulo para la operación de obtención.
  3. Si hay otros objetos en el depósito con el mismo código hash, entonces el método “clave” igual entra en juego.
    • Si equals() devuelve verdadero y es una operación put, entonces se anula el valor del objeto.
    • Si equals() devuelve falso y es una operación de colocación, entonces se agrega una nueva entrada al depósito.
    • Si equals() devuelve verdadero y es una operación de obtención, entonces se devuelve el valor del objeto.
    • Si equals() devuelve falso y es una operación de obtención, entonces se devuelve nulo.

La imagen de abajo muestra los elementos de un bucket de HashMap y cómo se relacionan sus equals() y hashCode(). El fenómeno cuando dos claves tienen el mismo código hash se llama colisión hash. Si el método hashCode() no se implementa correctamente, habrá una mayor cantidad de colisiones hash y las entradas del mapa no se distribuirán correctamente, lo que provocará lentitud en las operaciones de obtención y colocación. Esta es la razón por la que se utilizan números primos para generar el código hash, de modo que las entradas del mapa se distribuyan correctamente en todos los buckets.

¿Qué pasa si no implementamos hashCode() y equals()?

Ya hemos visto anteriormente que si no se implementa hashCode(), no podremos recuperar el valor porque HashMap usa el código hash para encontrar el contenedor donde buscar la entrada. Si solo usamos hashCode() y no implementamos equals(), tampoco se recuperará el valor porque el método equals() devolverá falso.

Mejores prácticas para implementar los métodos equals() y hashCode()

  • Utilice las mismas propiedades en las implementaciones de los métodos equals() y hashCode(), de modo que su contrato no se viole cuando se actualiza alguna propiedad.
  • Es mejor utilizar objetos inmutables como clave de tabla hash para que podamos almacenar en caché el código hash en lugar de calcularlo en cada llamada. Por eso, String es un buen candidato para la clave de tabla hash porque es inmutable y almacena en caché el valor del código hash.
  • Implemente el método hashCode() para que se produzca la menor cantidad de colisiones de hash y las entradas se distribuyan uniformemente entre todos los grupos.

Puedes descargar el código completo desde nuestro Repositorio de GitHub .

SUSCRÍBETE A NUESTRO BOLETÍN 
No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

Deja un comentario

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

Scroll al inicio