2011-07-20 14 views
9

Me estaba preguntando, ¿qué pasaría si la clave de un HashMap es mutable, programa de pruebas a continuación demuestran que y soy incapaz de entender cuando ambos iguales y hashCode métodos devuelve verdadero valor y el mismo, ¿por qué hashmap.containsKey devuelva false.Actualización de Java HashMap clave

public class MutableKeyHashMap { 

    public static void main(String []a){ 

      HashMap<Mutable, String> map = new HashMap<Mutable, String>(); 
      Mutable m1 = new Mutable(5); 
      map.put(m1, "m1"); 
      Mutable m2 = new Mutable(5); 
      System.out.println(map.containsKey(m2));  

      m2.setA(6); 
      m1.setA(6); 
      Mutable m3 = map.keySet().iterator().next(); 

      System.out.println(map.containsKey(m2)+" "+m3.hashCode()+"  "+m2.hashCode()+"  "+m3.equals(m2)); 

    } 
} 
class Mutable { 

    int a; 

    public Mutable(int a) { 

     this.a = a; 
    } 

    @Override 
    public boolean equals(Object obj) { 

     Mutable m = (Mutable) obj; 
     return m.a == this.a ? true : false; 
    } 

    @Override 
    public int hashCode(){ 
     return a; 
    } 

    public void setA(int a) { 

     this.a = a; 
    } 

    public int getA() { 
     return a; 
    } 
} 

Esta la salida:

cierto falsa 6 6 cierto

+0

Puede que este artículo sobre el enfoque de Python sea interesante: http://pyfaq.infogami.com/why-must-dictionary-keys-be-immutable – jtoberon

Respuesta

14

El javadoc lo explica

Nota: el gran cuidado debe tener cuidado si los objetos mutables son utilizado como claves de mapa El comportamiento de un mapa no se especifica si el valor de un objeto se cambia de una manera que afecta a las comparaciones iguales mientras el objeto es una clave en el mapa.

Básicamente, no utilice objetos mutables como claves en un mapa, usted va a quemarse

Para extrapolar, ya que los documentos pueden no aparecer clara, creo que el punto pertinente aquí es ` cambiado de una manera que afecta a igual a ', y parece estar asumiendo que se llama a igual (Objeto) cada vez que se invoca el contenido. Los documentos no dicen eso, la redacción implica que se les puede permitir hacer cálculos en caché.

Al mirar el source, parece que debido a que su código hash arroja un valor diferente (era 5, ahora es 6), es posible que se esté buscando en otro contenedor según los detalles de implementación.

+0

Comentando mi propia respuesta: me parece que el enlace la implementación no cumple con las obligaciones de la documentación, porque la mutabilidad del tipo en cuestión afecta a hashCode y no a igual. – ptomli

+0

Sí, pero una implementación de hashCode que es legal según el contrato definido por Object dará como resultado un comportamiento funcionalmente equivalente. – Affe

+0

No exactamente: "el método hashCode debe devolver consistentemente el mismo entero, siempre que no se modifique la información utilizada en comparaciones iguales en el objeto". Dado que los datos utilizados para iguales ha cambiado, también es permisible para que hashCode cambie. La documentación de Map implica que equals es el árbitro de la igualdad, mientras que la implementación usa hashCode, erróneamente, para encontrar una entrada, hasta donde yo sé. El único salvador aquí es que la documentación de Map no es exactamente clara acerca de cómo las implementaciones pueden usar iguales durante su vida útil. Y no sé la procedencia de la fuente vinculada. – ptomli

6

El HashMap pone su objeto en el lugar correspondiente para la clave de acceso directo 5. A continuación, cambie la clave a 6 y use containsKey para preguntar al mapa si contiene el objeto. El mapa mira la posición 6 y no encuentra nada, por lo que responde false.

Así que no hagas eso, entonces.

+0

Esto tiene sentido para mí. Seguí lo que dijiste y luego agregué 'map.put (m1," m1 ");' después de la línea 'm1.setA (6);'. Resultó en "verdadero verdadero 6 6 verdadero". – smslce

+0

Debe eliminarlo del mapa hash antes de cambiar la clave; de ​​lo contrario, creará una pérdida de memoria. – starblue

9

Puede pensar de esta manera, el mapa tiene 16 cubos. Cuando le das un objeto con A == 5, lo lanza al cubo 5. Ahora puedes cambiar A a 6, pero todavía está en el cubo 5. El mapa no sabe que cambiaste A, no reorganiza las cosas internamente.

Ahora vienes con otro objeto con A == 6, y le preguntas al mapa si tiene uno de esos. Va y busca en el cubo 6 y dice "No, nada allí". No va a ir a revisar todos los otros cubos por ti.

Obviamente, cómo se ponen las cosas en los cubos es más complicado que eso, pero así es como funciona en el núcleo.

0

Cuando pones "m1" la primera vez, hashCode() era 5. Por lo tanto, el HashMap usó 5 para colocar el valor en el depósito correspondiente. Después de cambiar m2, el hashCode() era 6, por lo que cuando trataste de buscar el valor que pusiste, el cubo que buscó fue diferente.

+0

a menos que el cambio de tamaño del mapa ocurra entre el momento en que mutaste y el momento en que intentaste buscarlo nuevamente. Cuando ocurre el cambio de tamaño, los hashCodes se vuelven a calcular. –

0

Un ejemplo de código para acompañar la respuesta de ptomli.

import java.util.*; 

class Elem { 
    private int n; 

    public Elem(int n) { 
     this.n = n; 
    } 

    public void setN(int n) { 
     this.n = n; 
    } 

    @Override 
    public int hashCode() { 
     return n; 
    } 

    @Override 
    public boolean equals(Object e) { 
     if (this == e) 
      return true; 
     if (!(e instanceof Elem)) 
      return false; 
     Elem an = (Elem) e; 
     return n == an.n; 
    } 
} 

public class MapTest { 
    public static void main (String [] args) { 
     Elem e1 = new Elem(1); 
     Elem e2 = new Elem(2); 

     HashMap map = new HashMap(); 
     map.put(e1, 100); 
     map.put(e2, 200); 

     System.out.println("before modification: " + map.get(e1)); 
     e1.setN(9); 
     System.out.println("after modification using updated key: " + map.get(e1)); 

     Elem e3 = new Elem(1); 
     System.out.println("after modification using key which equals to the original key: " + map.get(e3));  
    } 
} 

Compila y lo ejecuta. El resultado es:

before modification: 100 
after modification using updated key: null 
after modification using key which equals to the original key: null 

Estoy usando Java 6 en Linux.