Java tiene los métodos equals() y hashCode() 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 equals()
La clase Object define el método equals() de la siguiente manera:
public boolean equals(Object obj) {
return (this == obj);
}
Según la documentación de Java sobre el método equals(), cualquier implementación debe adherirse a los siguientes principios.
- Para cualquier objeto x,
x.equals(x)
debería devolvertrue
. - Para cualquier par de objetos x e y,
x.equals(y)
debería devolvertrue
solo siy.equals(x)
devuelvetrue
. - Para varios objetos x, y, y z, si
x.equals(y)
devuelvetrue
yy.equals(z)
devuelvetrue
, entoncesx.equals(z)
debería devolvertrue
. - Las múltiples invocaciones de
x.equals(y)
deberían devolver el mismo resultado, a menos que se modifique alguna de las propiedades del objeto que se está utilizando en la implementación deequals()
. - La implementación del método equals() de la clase Object devuelve
true
solo cuando ambas referencias apuntan al mismo objeto.
Java hashCode()
El método hashCode() de Java es un método nativo que devuelve el valor del código hash como un número entero del objeto. El contrato general del método hashCode() es:
- Multiple invocations of hashCode() should return the same integer value, unless the object property is modified that is being used in the equals() method.
- El valor del 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 son desiguales según el método equals(), no se requiere que sus códigos hash sean diferentes. Su valor de código hash puede ser igual o no.
Importancia del método 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)
, entonceso1.hashCode() == o2.hashCode()
siempre debe sertrue
. - Si
o1.hashCode() == o2.hashCode
es verdadero, no significa queo1.equals(o2)
serátrue
.
Cuándo sobrescribir los métodos equals() y hashCode()?
Cuando sobrescribimos el método equals(), es casi necesario sobrescribir también el método hashCode() para que su contrato no sea violado por nuestra implementación. Ten en cuenta que tu programa no lanzará ninguna excepción si se viola el contrato de equals() y hashCode(), si no tienes previsto utilizar la clase como clave de una tabla hash, entonces no creará ningún problema. Sin embargo, si planeas usar una clase como clave de una tabla hash, entonces es imprescindible sobrescribir tanto equals() como hashCode(). Veamos qué sucede cuando nos basamos en la implementación predeterminada de los métodos equals() y hashCode() y utilizamos una clase personalizada como clave de HashMap.
package com.journaldev.java;
public class DataKey {
private String name;
private int id;
// métodos getter y setter
@Override
public String toString() {
return "DataKey [name=" + name + ", id=" + id + "]";
}
}
package com.journaldev.java;
import java.util.HashMap;
import java.util.Map;
public class HashingTest {
public static void main(String[] args) {
Map<DataKey, 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 Map<DataKey, Integer> getAllData() {
Map<DataKey, 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, imprimirá null
. Esto se debe a que se utiliza el método hashCode() de Object para encontrar el cubo donde buscar la clave. Dado que no tenemos acceso a las claves de HashMap y estamos creando la clave nuevamente para recuperar los datos, notarás que los valores del código hash de ambos objetos son diferentes y, por lo tanto, no se encuentra el valor.
Implementación del método equals() y hashCode()
Podemos definir nuestra propia implementación de los métodos equals() y hashCode(), pero si no los implementamos cuidadosamente, puede haber problemas extraños en tiempo de ejecución. Afortunadamente, la mayoría de los IDEs de hoy en día proporcionan formas de implementarlos automáticamente y, si es necesario, podemos cambiarlos según nuestro requisito. Podemos usar Eclipse para generar automáticamente los métodos equals() y hashCode(). Aquí están las implementaciones de los métodos equals() y hashCode() generados automáticamente.
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public 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;
}
Nota que tanto los métodos equals() como hashCode() están utilizando los mismos campos para los cálculos, de modo que su contrato sigue siendo válido. Si ejecutas el programa de prueba nuevamente, obtendremos el objeto del mapa y el programa imprimirá 10. También podemos usar Project Lombok para generar automáticamente las implementaciones de los métodos equals() y hashCode().
¿Qué es una Colisión de Hash?
En términos muy simples, las implementaciones de tablas de hash en Java utilizan la siguiente lógica para las operaciones get y put.
- Primero identificar el “Bucket” a utilizar utilizando el código hash de la “clave”.
- Si no hay objetos presentes en el cubo con el mismo código hash, entonces agregue el objeto para la operación put y devuelva nulo para la operación get.
- Si hay otros objetos en el cubo con el mismo código hash, entonces entra en juego el método “equals” de la clave.
- Si “equals()” devuelve true y es una operación de inserción, entonces el valor del objeto se sobrescribe.
- Si “equals()” devuelve false y es una operación de inserción, entonces se agrega una nueva entrada al cubo.
- Si “equals()” devuelve true y es una operación de obtención, entonces se devuelve el valor del objeto.
- Si “equals()” devuelve false y es una operación de obtención, entonces se devuelve nulo.
A continuación, se muestra una imagen de los elementos de un cubo en un HashMap y cómo están relacionados sus métodos “equals()” y “hashCode()”. El fenómeno cuando dos claves tienen el mismo código hash se llama colisión de hash. Si el método “hashCode()” no está implementado correctamente, habrá un mayor número de colisiones de hash y las entradas del mapa no se distribuirán correctamente, causando lentitud en las operaciones de obtención e inserción. Esta es la razón por la que se utiliza un número primo en la generación del código hash para que las entradas del mapa se distribuyan adecuadamente en todos los cubos.
¿Qué pasa si no implementamos tanto hashCode() como equals()?
Ya hemos visto anteriormente que si no se implementa hashCode(), no podremos recuperar el valor porque HashMap utiliza el código hash para encontrar el cubo y 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 ambas implementaciones de los métodos equals() y hashCode(), para que su contrato no se viole cuando se actualiza alguna propiedad.
- Es mejor utilizar objetos inmutables como clave de la tabla hash para que podamos almacenar en caché el código hash en lugar de calcularlo en cada llamada. Por eso, String es una buena opción para clave de la tabla hash porque es inmutable y almacena en caché el valor del código hash.
- Implemente el método hashCode() de manera que se produzca el menor número posible de colisiones de hash y las entradas estén distribuidas uniformemente en todos los cubos.
Puedes descargar el código completo desde nuestro Repositorio de GitHub.
Source:
https://www.digitalocean.com/community/tutorials/java-equals-hashcode