Java HashMap containsKey retourne false pour un objet existant
j'ai une HashMap pour stocker des objets:
private Map<T, U> fields = Collections.synchronizedMap(new HashMap<T, U>());
mais, lorsque vous essayez de vérifier l'existence d'une clé, containsKey
retourne la méthode false
.equals
et hashCode
les méthodes sont implémentées, mais la clé n'est pas trouvée.
Lors du débogage d'un morceau de code:
return fields.containsKey(bean) && fields.get(bean).isChecked();
j'ai:
bean.hashCode() = 1979946475
fields.keySet().iterator().next().hashCode() = 1979946475
bean.equals(fields.keySet().iterator().next())= true
fields.keySet().iterator().next().equals(bean) = true
mais
fields.containsKey(bean) = false
Qu'est-ce qui pourrait causer un comportement aussi étrange?
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;
}
}
4 réponses
Vous ne devez pas modifier la clé après avoir inséré la carte.
Edit : j'ai trouvé l'extrait de javadoc Carte:
Note: il faut faire très attention si des objets mutables sont utilisés comme clés de carte. Le comportement d'une carte n'est pas spécifié si la valeur d'un objet est modifié d'une manière qui affecte égale comparaisons tandis que l'objet est un élément clé dans la carte.
exemple avec une classe de wrapper 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;
}
}
et le test :
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));
}
Sortie :
true
false
Note: Si vous ne surchargez pas hashcode (), alors vous obtiendrez true only
comme le souligne Arnaud Denoyelle, modifier une clé peut avoir cet effet. raison que containsKey
se soucie du seau de la clé dans la carte de hachage, alors que l'itérateur ne le fait pas. Si la première touche de votre carte --sans tenir compte des seaux -- se trouve être celle que vous voulez, alors vous pouvez obtenir le comportement que vous voyez. Si il n'y a qu'une seule entrée dans la carte, c'est bien sûr garanti.
Imaginez une simple carte à deux godets:
[0: empty] [1: yourKeyValue]
le itérateur qui va comme ceci:
- itérer sur tous les éléments dans un seau 0: il n'y a aucun
- parcourir tous les éléments dans un seau 1: juste un
yourKeyValue
containsKey
méthode, cependant, qui va comme ceci:
keyToFind
a unhashCode() == 0
, alors laissez-moi regarder dans un seau de 0 ( il n'). Oh, c'est vide -- retourfalse.
En fait, même si la clé reste dans le même seau, vous aurez avoir ce problème! Si vous regardez la mise en œuvre de HashMap
, vous verrez que chaque paire clé-valeur est stockée ainsi que le code de hachage de la clé. Quand la carte veut vérifier la clé stockée par rapport à une entrée, elle utilise à la fois ce hashCode et la clé est equals
:
((k = e.key) == key || (key != null && key.equals(k))))
c'est une belle optimisation, car cela signifie que les clés avec des hashCodes différents qui se produisent à entrer en collision dans le même seau seront vues comme non égale à très bon marché (juste un int
comparaison). Mais cela signifie aussi que changer la clé -- qui ne changera pas le stocké e.key
-- cassera la carte.
débogage du code source java j'ai réalisé que la méthode containsKey vérifie deux choses sur la clé recherchée par rapport à chaque élément de l'ensemble de clés: hashCode et égale; et il le fait dans cet ordre.
Cela signifie que si obj1.hashCode() != obj2.hashCode()
, il retourne false (sans évaluer obj1.equals (obj2). Mais, si obj1.hashCode() == obj2.hashCode()
, puis elle retourne obj1.equals(obj2)
Vous devez être sûr que les deux méthodes -peut-être que vous devez remplacer eux - true pour votre des critères définis.
Ici SSCCE
pour votre question ci-dessous. Il fonctionne comme un charme et il ne pouvait en être autrement, parce que votre hashCode
et equals
les méthodes semblent être autogénérées par IDE et elles ont l'air bien.
Donc, le mot clé est when debugging
. Le débogage lui-même peut nuire à vos données. Par exemple, quelque part dans la fenêtre de débogage vous définissez l'expression qui change votre fields
ou bean
objet. Après que vos autres expressions vous donnent résultat inattendu.
Essayez d'ajouter toutes ces vérifications à l'intérieur de votre méthode à partir de là où vous avez obtenu return
déclaration et imprimer leurs résultats.
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;
}
}