Java HashMap containsKey为现有对象返回false

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21600344/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 09:36:15  来源:igfitidea点击:

Java HashMap containsKey returns false for existing object

javahashmapcontainskey

提问by agad

I have a HashMap for storing objects:

我有一个用于存储对象的 HashMap:

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

but, when trying to check existence of a key, containsKeymethod returns false.
equalsand hashCodemethods are implemented, but the key is not found.
When debugging a piece of code:

但是,当尝试检查键是否存在时,containsKey方法返回false
equalshashCode方法都实现了,但是没有找到关键。
调试一段代码时:

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

I have:

我有:

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

but

fields.containsKey(bean) = false

What could cause such strange 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;
}


}

采纳答案by Arnaud Denoyelle

You shall not modify the key after having inserted it in the map.

将密钥插入地图后,您不得修改该密钥。

Edit : I found the extract of javadoc in Map:

编辑:我在Map 中找到了 javadoc 的摘录:

Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.

注意:如果将可变对象用作映射键,则必须非常小心。如果对象的值以影响等于比较的方式更改,而对象是映射中的键,则不会指定映射的行为。

Example with a simple wrapper class:

带有简单包装类的示例:

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

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

Output :

输出 :

true
false

Note : If you dont override hashcode() then you will get true only

注意:如果你不覆盖 hashcode() 那么你只会得到 true

回答by yshavit

As Arnaud Denoyelle points out, modifying a key can have this effect. The reasonis that containsKeycares about the key's bucket in the hash map, while the iterator doesn't. If the first key in your map --disregarding buckets -- just happens to be the one you want, then you can get the behavior you're seeing. If there's only one entry in the map, this is of course guaranteed.

正如 Arnaud Denoyelle 指出的那样,修改一个键可以产生这种效果。的原因是,containsKey大约在散列图关键的斗关心,而迭代器不会。如果地图中的第一个键 -- 忽略桶 -- 恰好是您想要的键,那么您可以获得您所看到的行为。如果地图中只有一个条目,这当然是有保证的。

Imagine a simple, two-bucket map:

想象一个简单的两桶地图:

[0: empty]  [1: yourKeyValue]

The iterator goes like this:

迭代器是这样的:

  • iterate over all of the elements in bucket 0: there are none
  • iterate over all the elements in bucket 1: just the one yourKeyValue
  • 迭代桶 0 中的所有元素:没有
  • 迭代桶 1 中的所有元素:只有一个 yourKeyValue

The containsKeymethod, however, goes like this:

containsKey然而,该方法是这样的:

  • keyToFindhas a hashCode() == 0, so let me look in bucket 0 (and onlythere). Oh, it's empty -- return false.
  • keyToFind有一个hashCode() == 0,所以让我看看存储桶 0(并且在那里)。哦,它是空的——返回false.

In fact, even if the key stays in the same bucket, you'll stillhave this problem! If you look at the implementation of HashMap, you'll see that each key-value pair is stored along with the key's hash code. When the map wants to check the stored key against an incoming one, it uses both this hashCode and the key's equals:

事实上,即使密钥留在同一个桶中,你仍然会遇到这个问题!如果查看 的实现HashMap,您会看到每个键值对都与键的哈希码一起存储。当映射想要根据传入的键检查存储的键时,它同时使用此 hashCode 和键的equals

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

This is a nice optimization, since it means that keys with different hashCodes that happen to collide into the same bucket will be seen as non-equal very cheaply (just an intcomparison). But it also means that changing the key -- which will not change the stored e.keyfield -- will break the map.

这是一个很好的优化,因为这意味着碰巧碰撞到同一个桶中的具有不同 hashCode 的键将被视为非常便宜的不相等(只是一个int比较)。但这也意味着更改键(不会更改存储的e.key字段)会破坏映射。

回答by Andremoniy

Here is SSCCEfor your issue bellow. It works like a charm and it couldn't be else, because your hashCodeand equalsmethods seem to be autogenerated by IDE and they look fine.

以下是SSCCE您的问题。它就像一个魅力,它不可能是其他的,因为您的hashCodeequals方法似乎是由 IDE 自动生成的,而且它们看起来很好。

So, the keyword is when debugging. Debug itself can harm your data. For example somewhere in debug window you set expression which changes your fieldsobject or beanobject. After that your other expressions will give you unexpected result.

所以,关键字是when debugging。调试本身可能会损害您的数据。例如,在调试窗口中的某处设置更改fields对象或bean对象的表达式。之后你的其他表达会给你意想不到的结果。

Try to add all this checks inside your method from where you got returnstatement and print out their results.

尝试将所有这些检查添加到您的方法中,从您获得return语句的位置并打印出它们的结果。

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


}

回答by Pedro García Medina

Debugging the java source code I realized that the method containsKey checks two things on the searched key against every element in the key set: hashCodeand equals; and it does it in that order.

调试java源代码我意识到该方法 containsKey 针对键集中的每个元素检查搜索键上的两件事: hashCodeequals;它按照这个顺序进行。

It means that if obj1.hashCode() != obj2.hashCode(), it returns false (without evaluating obj1.equals(obj2). But, if obj1.hashCode() == obj2.hashCode(), then it returns obj1.equals(obj2)

这意味着 if obj1.hashCode() != obj2.hashCode(),它返回 false (不评估 obj1.equals(obj2)。但是, if obj1.hashCode() == obj2.hashCode(),那么它返回obj1.equals(obj2)

You have to be sure that both methods -may be you have to override them- evaluate to true for your defined criteria.

您必须确保这两种方法 - 可能您必须覆盖它们 - 根据您定义的标准评估为 true 。