Java HashMap containsKey возвращает false для существующего объекта

у меня есть HashMap для хранения объектов:

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

но, при попытке проверить наличие ключа,containsKey возвращает false.
equals и hashCode методы реализованы, но ключ не найден.
При отладке фрагмента кода:

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

Я:

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

но

fields.containsKey(bean) = false

что могло вызвать такие странные behavioure?

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 ответов


вы не должны изменять ключ после того, как вставили его в карту.

Edit: я нашел экстракт javadoc в карта :

Примечание: необходимо проявлять большую осторожность, если изменяемые объекты используются в качестве ключей карты. Поведение карты не задается, если значение объекта изменяется таким образом, что влияет на сравнения equals, в то время как объект является ключом на карте.

пример с простым классом-оболочкой:

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

и тест :

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

выход :

true
false

Примечание: Если вы не переопределите hashcode (), то вы получите true только


как Арно Denoyelle указывает, изменяя ключ может иметь такой эффект. The причина это containsKey заботится о ведре ключа в хэш-карте, в то время как итератор этого не делает. Если первый ключ на вашей карте-не обращая внимания на ведра-именно тот, который вам нужен, тогда вы можете получить поведение, которое видите. Если на карте есть только одна запись, это, конечно, гарантировано.

представьте себе простую карту с двумя ведрами:

[0: empty]  [1: yourKeyValue]

в итератор выглядит так:

  • повторите все элементы в ведре 0: Нет
  • повторите все элементы в ведре 1: только один yourKeyValue

на containsKey метод, однако, выглядит так:

  • keyToFind есть hashCode() == 0, поэтому позвольте мне посмотреть в ведра 0 (и только есть). О, она пуста-вернись!--7-->

в самом деле, даже если ключ остается в том же ведро, ты будешь!--13-->еще есть такая проблема! Если вы посмотрите на реализацию HashMap, вы увидите, что каждая пара ключ-значение хранится вместе с хэш-кодом ключа. Когда карта хочет проверить сохраненный ключ на входящий, она использует этот hashCode и ключа equals:

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

это хорошая оптимизация, так как это означает, что ключи с разными хэш-кодами, которые сталкиваются в одном ведре, будут рассматривается как неравный очень дешево (просто int сравнение). Но это также означает, что изменение ключа - который не изменит сохраненный e.key поле -- разобьет карту.


отладка исходного кода java я понял, что метод containsKey проверяет две вещи на искомом ключе против каждого элемента в наборе ключей: хэш-код и равна; и он делает это в таком порядке.

Это означает, что если obj1.hashCode() != obj2.hashCode(), он возвращает false (без оценки obj1.метод Equals(obj2). Но если ... --1-->, то возвращается obj1.equals(obj2)

вы должны быть уверены, что оба метода -может быть, вы должны переопределить их значение true для вашего определяющий критерий.


здесь SSCCE на ваш вопрос ниже. Это работает как шарм, и это не может быть иначе, потому что ваш hashCode и equals методы кажутся автогенерируемые на IDE и они выглядят нормально.

Итак, ключевое слово when debugging. Сама отладка может повредить ваши данные. Например, где-то в окне отладки вы устанавливаете выражение, которое изменяет ваш fields объект или