¿Son las claves mutables de hashmap una práctica peligrosa?
¿Es una mala práctica utilizar objetos mutables como claves Hashmap? ¿Qué sucede cuando intentas recuperar un valor de un Hashmap usando una clave que se ha modificado lo suficiente como para cambiar su código hash?
Por ejemplo, dado
class Key
{
int a; //mutable field
int b; //mutable field
public int hashcode()
return foo(a, b);
// setters setA and setB omitted for brevity
}
con codigo
HashMap<Key, Value> map = new HashMap<Key, Value>();
Key key1 = new Key(0, 0);
map.put(key1, value1); // value1 is an instance of Value
key1.setA(5);
key1.setB(10);
¿Qué pasa si ahora llamamos map.get(key1)
? ¿Es esto seguro o aconsejable? ¿O el comportamiento depende del idioma?
Muchos desarrolladores respetados, como Brian Goetz y Josh Bloch, han señalado que:
Si el valor hashCode() de un objeto puede cambiar según su estado, entonces debemos tener cuidado al usar objetos como claves en colecciones basadas en hash para asegurarnos de que no permitimos que su estado cambie cuando se usan como claves hash. . Todas las colecciones basadas en hash asumen que el valor hash de un objeto no cambia mientras está en uso como clave en la colección. Si el código hash de una clave cambiara mientras estaba en una colección, podrían producirse algunas consecuencias impredecibles y confusas. Por lo general, esto no es un problema en la práctica; no es una práctica común usar un objeto mutable como una Lista como clave en un HashMap.
Esto no es seguro ni aconsejable. El valor asignado por key1 nunca se puede recuperar. Al realizar una recuperación, la mayoría de los mapas hash harán algo como
Object get(Object key) {
int hash = key.hashCode();
//simplified, ignores hash collisions,
Entry entry = getEntry(hash);
if(entry != null && entry.getKey().equals(key)) {
return entry.getValue();
}
return null;
}
En este ejemplo, key1.hashcode() ahora apunta al depósito incorrecto de la tabla hash y no podrá recuperar el valor1 con la clave1.
Si hubieras hecho algo como,
Key key1 = new Key(0, 0);
map.put(key1, value1);
key1.setA(5);
Key key2 = new Key(0, 0);
map.get(key2);
Esto tampoco recuperará el valor1, ya que la clave1 y la clave2 ya no son iguales, por lo que esta verificación
if(entry != null && entry.getKey().equals(key))
fallará.