web-dev-qa-db-fra.com

Java HashMap containsKey renvoie false pour l'objet existant

J'ai un HashMap pour stocker des objets:

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

mais, en essayant de vérifier l'existence d'une clé, la méthode containsKey renvoie false.
Les méthodes equals et hashCode sont implémentées, mais la clé est introuvable.
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;
}


}
30
agad

Vous ne devez pas modifier la clé après l'avoir insérée dans la carte.

Edit: j'ai trouvé l'extrait de javadoc dans Carte :

Remarque: une grande prudence doit être exercée 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ée d'une manière qui affecte des comparaisons égales alors que l'objet est une clé dans la carte.

Exemple avec une classe 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

Remarque: Si vous ne remplacez pas hashcode (), vous obtiendrez vrai uniquement

24
Arnaud Denoyelle

Comme le souligne Arnaud Denoyelle, la modification d'une clé peut avoir cet effet. La raison est que containsKey se soucie du compartiment de la clé dans la carte de hachage, contrairement à l'itérateur. Si la première clé de votre carte - sans tenir compte des compartiments - se trouve être celle que vous souhaitez, vous pouvez obtenir le comportement que vous voyez. S'il n'y a qu'une seule entrée sur la carte, c'est bien sûr garanti.

Imaginez une carte simple à deux compartiments:

[0: empty]  [1: yourKeyValue]

L'itérateur va comme ceci:

  • itérer sur tous les éléments du compartiment 0: il n'y en a pas
  • itérer sur tous les éléments du bucket 1: juste celui yourKeyValue

Cependant, la méthode containsKey se présente comme suit:

  • keyToFind a une hashCode() == 0, alors laissez-moi regarder dans le compartiment 0 (et seulement là-bas). Oh, c'est vide - retournez false.

En fait, même si la clé reste dans le même compartiment, vous aurez toujours ce problème! Si vous regardez l'implémentation de HashMap, vous verrez que chaque paire clé-valeur est stockée avec le code de hachage de la clé . Lorsque la carte veut comparer la clé stockée avec une clé entrante, elle utilise à la fois ce hashCode et le equals de la clé:

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

Il s'agit d'une optimisation intéressante, car cela signifie que les clés avec des codes de hachage différents qui entrent en collision dans le même compartiment seront considérées comme non égales à très bon marché (juste une comparaison int). Mais cela signifie également que changer la clé - qui ne changera pas le champ stocké e.key - rompra la carte.

10
yshavit

Débogage du Java J'ai réalisé que la méthode containsKey vérifie deux choses sur la clé recherchée par rapport à chaque élément du jeu de clés: hashCode et égal; 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(), alors il retourne obj1.equals(obj2)

Vous devez être sûr que les deux méthodes -peut-être devez-vous les remplacer- évaluent à vrai pour vos critères définis.

5

Voici SSCCE pour votre problème ci-dessous. Cela fonctionne comme un charme et il ne pourrait pas en être autrement, car vos méthodes hashCode et equals semblent être générées automatiquement par IDE et elles semblent bien.

Ainsi, le mot clé est when debugging. Le débogage lui-même peut endommager vos données. Par exemple, quelque part dans la fenêtre de débogage, vous définissez une expression qui modifie votre objet fields ou bean objet. Après cela, vos autres expressions vous donneront un résultat inattendu.

Essayez d'ajouter toutes ces vérifications à l'intérieur de votre méthode d'où vous avez obtenu la déclaration return et imprimez 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;
    }


}
1
Andremoniy