java hashmap containskey

containskey java



Java HashMap containsKey devuelve falso para el objeto existente (4)

Tengo un HashMap para almacenar objetos:

private Map<T, U> fields = Collections.synchronizedMap(new HashMap<T, U>());

pero, al intentar verificar la existencia de una clave, el método containsKey devuelve false .
hashCode métodos equals y hashCode están implementados, pero la clave no se encuentra.
Al depurar una pieza de código:

return fields.containsKey(bean) && fields.get(bean).isChecked();

Yo tengo:

bean.hashCode() = 1979946475 fields.keySet().iterator().next().hashCode() = 1979946475 bean.equals(fields.keySet().iterator().next())= true fields.keySet().iterator().next().equals(bean) = true

pero

fields.containsKey(bean) = false

¿Qué podría causar un comportamiento tan extraño?

public class Address extends DtoImpl<Long, Long> implements Serializable{ <fields> <getters and setters> @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + StringUtils.trimToEmpty(street).hashCode(); result = prime * result + StringUtils.trimToEmpty(town).hashCode(); result = prime * result + StringUtils.trimToEmpty(code).hashCode(); result = prime * result + ((country == null) ? 0 : country.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Address other = (Address) obj; if (!StringUtils.trimToEmpty(street).equals(StringUtils.trimToEmpty(other.getStreet()))) return false; if (!StringUtils.trimToEmpty(town).equals(StringUtils.trimToEmpty(other.getTown()))) return false; if (!StringUtils.trimToEmpty(code).equals(StringUtils.trimToEmpty(other.getCode()))) return false; if (country == null) { if (other.country != null) return false; } else if (!country.equals(other.country)) return false; return true; } }


Aquí está SSCCE para su problema abajo. Funciona como un amuleto y no podría ser más, porque los métodos hashCode e equals parecen ser autogenerados por IDE y se ven bien.

Entonces, la palabra clave es when debugging . La depuración en sí misma puede dañar sus datos. Por ejemplo, en algún lugar de la ventana de depuración puede establecer una expresión que cambie su objeto de fields o bean . Después de eso, tus otras expresiones te darán resultados inesperados.

Intente agregar todas estas comprobaciones dentro de su método desde donde obtuvo la declaración de return e imprima sus resultados.

import org.apache.commons.lang.StringUtils; import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.Map; public class Q21600344 { public static void main(String[] args) { MapClass<Address, Checkable> mapClass = new MapClass<>(); mapClass.put(new Address("a", "b", "c", "d"), new Checkable() { @Override public boolean isChecked() { return true; } }); System.out.println(mapClass.isChecked(new Address("a", "b", "c", "d"))); } } interface Checkable { boolean isChecked(); } class MapClass<T, U extends Checkable> { private Map<T, U> fields = Collections.synchronizedMap(new HashMap<T, U>()); public boolean isChecked(T bean) { return fields.containsKey(bean) && fields.get(bean).isChecked(); } public void put(T t, U u) { fields.put(t, u); } } class Address implements Serializable { private String street; private String town; private String code; private String country; Address(String street, String town, String code, String country) { this.street = street; this.town = town; this.code = code; this.country = country; } String getStreet() { return street; } String getTown() { return town; } String getCode() { return code; } String getCountry() { return country; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + StringUtils.trimToEmpty(street).hashCode(); result = prime * result + StringUtils.trimToEmpty(town).hashCode(); result = prime * result + StringUtils.trimToEmpty(code).hashCode(); result = prime * result + ((country == null) ? 0 : country.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Address other = (Address) obj; if (!StringUtils.trimToEmpty(street).equals(StringUtils.trimToEmpty(other.getStreet()))) return false; if (!StringUtils.trimToEmpty(town).equals(StringUtils.trimToEmpty(other.getTown()))) return false; if (!StringUtils.trimToEmpty(code).equals(StringUtils.trimToEmpty(other.getCode()))) return false; if (country == null) { if (other.country != null) return false; } else if (!country.equals(other.country)) return false; return true; } }


Como señala Arnaud Denoyelle, modificar una clave puede tener este efecto. La razón es que containsKey preocupa por el cubo de la clave en el mapa hash, mientras que el iterador no. Si la primera clave en su mapa, sin tener en cuenta los segmentos, es la que desea, entonces puede obtener el comportamiento que está viendo. Si solo hay una entrada en el mapa, esto está por supuesto garantizado.

Imagine un mapa simple de dos cubos:

[0: empty] [1: yourKeyValue]

El iterador dice así:

  • iterar sobre todos los elementos en el segmento 0: no hay ninguno
  • itere sobre todos los elementos en el yourKeyValue 1: solo el que yourKeyValue

El método containsKey , sin embargo, es así:

  • keyToFind tiene un hashCode() == 0 , así que déjame buscar en el cubo 0 (y solo allí). Oh, está vacío - devuelve false.

De hecho, incluso si la clave permanece en el mismo contenedor, ¡ todavía tendrá este problema! Si observa la implementación de HashMap , verá que cada par clave-valor se almacena junto con el código hash de la clave . Cuando el mapa quiere verificar la clave almacenada contra una entrante, usa tanto este hashCode como los equals la clave :

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

Esta es una buena optimización, ya que significa que las claves con diferentes hashCodes que chocan en el mismo cubo se verán como no iguales a muy bajo costo (solo una comparación int ). Pero también significa que al cambiar la clave, que no cambiará el campo de la e.key guardada, se romperá el mapa.


No deberá modificar la clave después de haberla insertado en el mapa.

Editar: Encontré el extracto de javadoc en Mapa :

Nota: se debe tener mucho cuidado si los objetos mutables se utilizan 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.

Ejemplo con una clase de contenedor simple:

public static class MyWrapper { private int i; public MyWrapper(int i) { this.i = i; } public void setI(int i) { this.i = i; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; return i == ((MyWrapper) o).i; } @Override public int hashCode() { return i; } }

y la prueba:

public static void main(String[] args) throws Exception { Map<MyWrapper, String> map = new HashMap<MyWrapper, String>(); MyWrapper wrapper = new MyWrapper(1); map.put(wrapper, "hello"); System.out.println(map.containsKey(wrapper)); wrapper.setI(2); System.out.println(map.containsKey(wrapper)); }

Salida:

true false


Al depurar el código fuente de Java, me di cuenta de que el método containsKey comprueba dos cosas en la clave buscada contra cada elemento en el conjunto de claves: hashCode y equals ; y lo hace en ese orden.

Significa que si obj1.hashCode() != obj2.hashCode() , devuelve false (sin evaluar obj1.equals (obj2). Pero, si obj1.hashCode() == obj2.hashCode() , devuelve obj1.equals(obj2)

Debe asegurarse de que ambos métodos -puede ser que los tenga que anular- evalúan como verdaderos para sus criterios definidos.