Cálculo correcto del hashCode en Java


Cuando definimos un objeto y redefinimos el método #equals debemos redefinir el método #hashCode

Definir mal el equals es fatal, pero quiero hablar del método #hashCode. Este método devuelve un número con una propiedad fundamental, si dos objetos son iguales (según #equals) el valor retornado por sus respectivos hashCode debe ser igual. Básicamente esta expresión debe ser siempre verdadera:
(!o1.equals(o2)) || (o1.hashCode() == o2.hashCode())
Esto implica que si dos objetos no son iguales (según #equals) pueden tener o no el mismo valor de hashCode.

Implementar mal el hashCode puede pasar inadvertido pero pasa a ser fatal si se usan esos objetos como claves en un HashMap o si se almacenan en un HashSet.

El problema está en que cuando metemos uno de estos objetos en una de esas colecciones se los almacena en diferentes sectores (llamados buckets) según su valor de hashCode. Luego cuando los queremos recuperar pasando como referencia otra instancia que es igual como su valor de hashCode no coincide lo busca en otro sector y no lo encuentra.

Implementar el hashCode de cualquier manera que cumpla la condición mencionada antes tampoco es suficiente, porque si todos los objetos tienen el mismo valor de hashCode van a ir a parar al mismo sector y el HashSet o el HashMap tendrá la misma estructura y tiempos de respuesta de una lista.

Una forma de quedarnos tranquilos de que el valor de hashCode cumple las propiedades y no transforma los HashMaps en listas es haciendo así el cálculo:

public static int genericHash(int... fieldHashes) {
int result = 17;
for (int hash : fieldHashes) {
result = 37 * result + hash;
}
return result;
}

Y un ejemplo de su uso en un objeto:

public class Persona {
private String tipoDocumento;
private int numeroDocumento;
private int hashCode = 0;

@Override
public int hashCode() {
if (this.hashCode == 0) {
this.hashCode = genericHash(this.tipoDocumento.hashCode(),
this.numeroDocumento);
}
return this.hashCode;
}

@Override
public boolean equals(Object obj) {
return obj instanceof Persona
&& ((Persona) obj).tipoDocumento.equals(this.tipoDocumento)
&& ((Persona) obj).numeroDocumento == this.numeroDocumento;
}
}
Anuncios

6 pensamientos en “Cálculo correcto del hashCode en Java

  1. Creo que Joshua Bloch utiliza 31 en vez de 37. Al menos en la segunda edición de “Effective Java”. Pero gracias por el artículo

  2. Yo soy un amigo de las cosas “para todo” y a mi me está funcionando bien con esto:
    protected static int genericHash() {
    int result = 17;
    Field [] thisfields=Class.class.getClass().getDeclaredFields();
    HashMap fieldHashes=new HashMap();
    for(Field f:thisfields)
    fieldHashes.put(f.getName(), f.hashCode());
    for (int hash : fieldHashes.values())
    result = 37 * result + hash;
    return result;
    }//genericHash
    @Override
    public int hashCode(){
    return genericHash();
    }

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s