2009-07-09 46 views
14

Estoy intentando localizar una clave en un HashMap. Puedo imprimir la clave seleccionada usando 'get', pero cuando uso 'containsKey' en una declaración if, no se encuentra.Java HashMap get funciona pero containsKey no

SÉ que la clave está presente en el Mapa pero sigue volviendo en falso. ¿Alguna gente de ideas?

Mi código:

public static boolean checkLowerStructuralSupport(Location location) { 

    boolean hasSupport = false; 

    Location supportingLocation = new Location(location.getX(), location.getY(), location.getZ() - 1); 

    System.out.println(_levels.get(supportingLocation.getZ()).getLevelSites2().get(supportingLocation)); //works 

    if (_levels.get(supportingLocation.getZ()).getLevelSites2().containsKey(supportingLocation)) { 
     hasSupport = true; 
    } else { 
     hasSupport = false; 
    } 

    return hasSupport; 
} 

Aquí está el código de la clase Ubicación:

public class Location { 

    protected int _x; 
    protected int _y; 
    protected int _z; 

    public Location(int xAxis, int yAxis, int zAxis) { 
     this._x = xAxis; 
     this._y = yAxis; 
     this._z = zAxis; 
    } 

    public void equals() { 
     //not implemented yet 
    } 

    public void HashCode() { 
     //not implemented yet 
    } 

    public String toString() { 
     String locationString = Integer.toString(_x) + Integer.toString(_y) + Integer.toString(_z); 
     return locationString; 
    } 

    public void setX(int XAxis) { 
     this._x = XAxis; 
    } 

    public int getX() { 
     return this._x; 
    } 

    public void setY(int YAxis) { 
     this._y = YAxis; 
    } 

    public int getY() { 
     return this._y; 
    } 

    public void setZ(int ZAxis) { 
     this._z = ZAxis; 
    } 

    public int getZ() { 
     return this._z; 
    } 

} 
+0

¿Es este el código exacto que estás ejecutando? De lo contrario, proporcione el código exacto que está usando (solo lo hago porque en otra pregunta, lo que usted proporcionó no era representativo de su problema y causó mucha confusión). –

+0

Además, ¿este comportamiento es consistente? Es decir, get (...) * always * devuelve algo cuando se espera, y containsKey (...) * never * does? –

+0

¿Está desarrollando con un JDK que incluye el código fuente? Si es así, quizás deba intentar depurar el código y entrar en los métodos de HashMap (containsKey y get) para ver si puede encontrar un comportamiento inusual. No te preocupes, estos métodos son terriblemente complejos. –

Respuesta

23

Debe asegurarse de que la clase Location ha implementado adecuadamente sus hashCode() y equals(Object) métodos (documentation). Es decir, si dos objetos Location son efectivamente iguales, deberían compartir un código hash común y su método equals debería devolver true.

+1

Pero entonces ¿por qué get() funciona y containsKey() no? – butterchicken

+1

@butterchicken: Tal vez get() no utiliza la optimización "hashCode" para obtener el valor, mientras que "containsKey" sí lo es. –

+0

He usado containsKey de manera efectiva en otros métodos .... frazzled – burntsugar

2

En Ubicación clase, asegúrese de que está sustituyendo hashCode y es igual a métodos.

Si lo es, ¿puede publicarlos?

1

Ambos get() y containsKey() utilizan el método Location de la clase hashCode(). El método equals() no se llama a menos que haya una colisión hash. (Por lo tanto, obtener de HashMap() no utilizará equals() en cada situación.)

para su clase Location, lo hizo por casualidad pasar a poner en práctica su propia versión de hashCode()? El método hashCode() debe implementarse cuidadosamente. Joshua Bloch describió todos los detalles en el libro Effective Java, partes de las cuales están en línea ... Buscaré el enlace a esos capítulos de muestra: Effective Java Sample Chapters. Desea el capítulo 3.

Como pregunté en el comentario a la pregunta, ¿de dónde viene su variable _levels? No veo que se declare dentro de ese método y su nombre (prefijo de subrayado, ¿está importando esa convención de algún otro idioma?) Sugiere que "vive" fuera de este método. Tal vez otro código lo está cambiando durante la ejecución? Por favor, háganos saber cuándo lo resuelve; el suspenso me está matando.

+2

Creo que ambos usan iguales(). Tenga en cuenta que es perfectamente legal que dos instancias de un objeto devuelvan 'hashCode()'. De hecho, ni siquiera es ilegal que 'hashCode()' simplemente devuelva la misma constante (por ejemplo, 0), realmente muy poco óptimo. –

+0

No: busque en la fuente de JDK: get y containsKey (a través de getEntry) ambos verifican la igualdad de clave (a menos que la identidad de clave sea la misma) en la línea: if (e.hash == hash && ((k = e.key) == key || key.equals (k))) – paulcm

+1

igual se llamará durante un get si hay una colisión hash. –

2

containsKey utiliza el método igual para comparar el param con las entradas en el conjunto de claves. Entonces la clase Location necesita tener un método igual que sea bueno. El método predeterminado es igual en java.lang.Object solo devuelve verdadero cuando ambos objetos son el mismo objeto. En este caso, probablemente tenga 2 instancias diferentes que necesiten ser comparadas y necesite un método de igual a medida.

1

Para evitar problemas, sus métodos equals() y hashCode() deben ser consistentes y cumplir con los requisitos (como se indica en otra parte).

Además, hashCode() debe no dependen de los miembros mutables, de lo contrario su código hash calculado puede cambiar, y esto afecta el funcionamiento interno de la HashMap. Eso se revelará en la imposibilidad de recuperar material de las colecciones Hash*.

5

Como se describe aquí, debe sobrescribir el método equals (Object).

La razón por la que get (Object) está funcionando es que HashMap calculará el Hash para su clase de ubicación y devolverá el objeto al que el hascode apunta.

containsKey (Object) calcula la tecla hash y obtiene el objeto al que apunta el hash. El objeto del HashMap se comparará con el Objeto que ingresó. Para estas comparaciones, se usa el método igual. Cuando no anula el método igual, se devuelve verdadero cuando el objeto hace referencia a la misma instancia.

De HashMap

/** 
* Check for equality of non-null reference x and possibly-null y. 
*/ 
static boolean eq(Object x, Object y) { 
    return x == y || x.equals(y); 
} 

desde objeto

public boolean equals(Object obj) { 
    return (this == obj); 
    } 

Desde el javadoc de iguales

El método equals de la clase Object implementa el más discriminante ing posible relación de equivalencia en objetos ; es decir, para cualquier valor de referencia no nulo xey, este método devuelve verdadero si y solo si xy se refieren al mismo objeto (x == y tiene el valor verdadero).

Tenga en cuenta que por lo general es necesario reemplazar el método hashCode siempre se anula este método, a fin de mantener el contrato general para el método hashCode, que establece que objetos iguales deben tener códigos iguales de hash .

+3

Podría estar equivocado, pero estoy bastante seguro de que (...) también comparará la clave (Ubicación) con la igualdad, y usará el hash solo para ubicarlo rápidamente (reducirá el número de comparaciones). ¿De qué otra forma distinguiría entre múltiples objetos con el mismo hash (que es perfectamente legal)? –

2

La única cosa que puedo pensar en que hará que esto es si el estado de supportingLocation alguna manera se está mutado entre la llamada y la get(...)containsKey(...).

Suponiendo que el fragmento de código que envió es el código exacto que está causando problemas, el único lugar que podría ocurrir es si uno de Location#getZ(...), Location#hashCode() o Location#equals(Object) muta el estado de Localización (o la ubicación del constructor, o uno de estos métodos comienza un hilo que cambia aleatoriamente el estado de la instancia de Ubicación, pero creo que podemos descartarlo).

¿Podría verificar que ninguno de los métodos anteriores está cambiando el estado de la instancia supportingLocation? Si bien no estoy familiarizado con la clase Location en sí, me atrevo a adivinar que una clase así sería idealmente inmutable.

Editar: para aclarar, cuando digo que Location#getZ() etc no están mutando la ubicación, lo que quiero decir es:

Location x = new Location(1,2,3); 
Location y = new Location(1,2,3); 

boolean eq1 = x.equals(y); 
int hash1 = x.hashCode(); 
x.getZ(); // this should *not* mutate the state of x 
boolean eq2 = x.equals(y); 
int hash2 = x.hashCode(); 

Al final, EQ1 debe ser igual a EQ1, y debe ser hash1 igual a hash2.Si este no es el caso, getZ() está mutando el estado de x (o igual, o hashCode, o peor, esos métodos están completamente desactivados), y dará como resultado el comportamiento que observó.

+0

El código que he publicado es la implementación exacta. La clase de ubicación tiene métodos de mutador. Aclamaciones. – burntsugar

+0

La mutación no debería importar. equals y hashCode no se han reemplazado, por lo que se usarán las versiones predeterminadas, y solo miran el puntero, no el estado del objeto. – finnw

+0

Sí, estoy de acuerdo, mi respuesta fue publicada antes de que se publicara el código fuente de la ubicación completa. Ahora no entiendo lo que está mal, tal vez una implementación 'Map 'pícara, o un error en' getLevelSites2() '. –

1

Obtenga un pico en el código fuente para la implementación de HashMap. Both get y containsKey usan los métodos hasCode() y equals() de su objeto clave.

La única diferencia real, y como se ha señalado, es un cheque nulo trivial, es en las comparaciones:

get:

((k = e.key) == key || key.equals(k)) 

containsKey:

((k = e.key) == key || (key != null && key.equals(k))) 

donde e es de tipo Entrada para un HashMap.

Por lo tanto, si no tiene implementaciones sólidas de hashCode() y/o iguales(), tendrá un problema. Además, si tus claves fueron mutadas (veo que no declaraste que los campos de clase son definitivos) podrías tener un problema.

Tome el ejemplo siguiente:

public class HashMapTest { 
    static class KeyCheck { 
     int value; 
     public KeyCheck(int value) { this.value = value; } 
     public void setValue(int value) { this.value = value; } 
     @Override public int hashCode() { return value; } 
     @Override public boolean equals(Object o) { 
      return ((KeyCheck)o).value == this.value; 
     } 
    } 

    public static void main(String args[]) { 
     HashMap<KeyCheck, String> map = new HashMap<KeyCheck, String>(); 
     KeyCheck k1 = new KeyCheck(5); 
     KeyCheck k2 = new KeyCheck(5); 

     map.put(k1, "Success"); 

     System.out.println("Key: " + k1 + " Get: " + map.get(k1) + 
          " Contains: " + map.containsKey(k1)); 
     System.out.println("Key: " + k2 + " Get: " + map.get(k2) + 
          " Contains: " + map.containsKey(k2)); 

     k1.setValue(10); 

     System.out.println("Key: " + k1 + " Get: " + map.get(k1) + 
          " Contains: " + map.containsKey(k1)); 
     System.out.println("Key: " + k2 + " Get: " + map.get(k2) + 
          " Contains: " + map.containsKey(k2)); 
    } 
} 

Esto imprimirá:

Clave: HashMapTest $ KeyCheck @ 5 Obtener: Éxito contiene: verdadero
Clave: HashMapTest $ KeyCheck @ 5 Get : El éxito contiene: true
Clave: HashMapTest $ KeyCheck @ a Get: null Contiene: falso
Clave: HashMapTest $ KeyCheck @ 5 Get: null Contiene: falso

Como puede ver, en este caso la mutabilidad provocó que el hashCode() cambiara, lo que arruinó todo.

+0

No creo que la diferencia entre get y containsKey sea significativa, porque solo se aplica cuando la clave es nula, y en ese caso get tiene una ruta de código de caso especial (que ya habrá retornado). Sin embargo, su idea de mutar los valores clave está bien hecha. – paulcm

0

creo que en algún momento necesita el código hash ya veces no tan pienso que de esta manera se puede convertir del código hash comprobación cuando se quiere comprar cambiar el código hash de todos los objetos que desea 0

public class sample(){ 
    @JsonIgnore 
    private int hashCode = super.hashCode(); 

    public void setHashCode(int hashCode){ 
     this.hashCode = hashCode; 
    }  

    @Override 
    public int hashCode(){ 
     return this.hashCode; 
    }  

    @Override 
    public boolean equals(Object obj) { 
     if (obj == null) { 
      return false; 
     } 
     if (getClass() != obj.getClass()) { 
      return false; 
     } 
     final ReflectObject other = (ReflectObject) obj; 
     if (this.hashCode != other.hashCode) { 
      return false; 
     } 
     return true; 
    } 
}