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;
}


}
26
demandé sur Andremoniy 2014-02-06 14:33:48

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

19
répondu Arnaud Denoyelle 2018-06-30 06:28:14

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 un hashCode() == 0, alors laissez-moi regarder dans un seau de 0 ( il n'). Oh, c'est vide -- retour false.

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.

10
répondu yshavit 2014-02-06 11:12:01

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.

3
répondu Pedro García Medina 2014-06-03 22:15:48

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;
    }


}
1
répondu Andremoniy 2014-02-06 11:07:08