Es importante hacer override al método equals() y hashCode correctamente si se quiere usar con clases como HashMap y HashSet. El siguiente ejemplo podrás entender que puede salir mal usando colecciones tales como HashSet.
import java.util. * ;
class Circle {
private int xPos, yPos, radius;
public Circle(int x, int y, int r) {
xPos = x;
yPos = y;
radius = r;
}
public boolean equals(Object arg) {
if (arg == null) return false;
if (this == arg) return true;
if (arg instanceof Circle) {
Circle that = (Circle) arg;
if ((this.xPos == that.xPos) && (this.yPos == that.yPos) && (this.radius == that.radius)) {
return true;
}
}
return false;
}
}
class TestCircle {
public static void main(String[] args) {
Set circleList = new HashSet ();
circleList.add(new Circle(10, 20, 5));
System.out.println(circleList.contains(new Circle(10, 20, 5)));
}
}
Imprime false!! pero ¿porque?. La clase Circle tiene el método equals overrided pero no el método hashCode. Cuando usas objetos de la clase Circle dentro de contenedores estándar, se convierte en un problema, ya que para las búsquedas rápidas el contenedor compara el hashcode de los objetos. Si no le hemos hecho un override al método hashCode() el contenedor no encontrara el objeto, incluso si un objeto con el mismo contenido fue pasado. Por lo tanto necesitamos hacer un override del método hashCode.
¿Como hacer un override al método hashCode correctamente?. En el caso ideal el método debe devolver un único hash para diferentes objetos.El método hashCode() debe retornar el mismo hash si el método equals() devuelve true. Pero ¿qué sucede si los objetos son diferentes? ¿debe retornar equalsfalse?. El método hashCode y equals deben ser consistentes para una clase, y para efectos prácticos asegurate de seguir la siguiente regla: El método hashCode() debe retornar el mismo valor hash para dos objetos si equals() devuelve verdadero para ambos. Veamos un ejemplo de cómo implementarlo.
public int hashCode() {
// use bit-manipulation operators such as ^ to generate close to unique
// hash codes here we are using the magic numbers 7, 11 and 53,
// but you can use any numbers, preferably primes
return (7 * xPos) ^ (11 * yPos) ^ (53 * yPos);
}
Cuando implementamos el método puedes usar los valores de los miembros de la instancia para generar tu hash. Ahora si ejecutamos main, imprime true. En la implementación, los valores son multiplicados por un número primitivo y también un operador bit-wise (operador a nivel bit). Tu puedes usar un método más complejo para generar un hash, pero por el momento esto es suficiente.
Te preguntarás ¿qué sucede para otros tipos de datos, como float o tipos referenciados?. Para tipo de datos de punto flotante veamos un ejemplo usando java.awt.Point2D, el cual sus valores x,y son double. Los métodos getX() y getY() devuelve los valores de x y y respectivamente.
public int hashCode() {
long bits = java.lang.Double.doubleToLongBits(getX());
bits ^= java.lang.Double.doubleToLongBits(getY()) * 31;
return (((int) bits) ^ ((int)(bits >> 32)));
}
Esta implementación usa el método doubleToLongBits(), el cual toma un double y retorna un long. Por lo tanto tomamos los valores de los bits como long y los manipulamos para obtener nuestro hash.
Por último ¿cómo generamos un hash si la clase tiene un miembro de tipo referenciado?. Consideremos usar una instancia de la clase Point en lugar de xPos y yPost.
class Circle {
private int radius;
private Point center;
// other members elided
}
En este caso tu puedes usar el método hashCode de la clase Point para generar el de la clase Circle.
public int hashCode() {
return center.hashCode() ^ radius;
}