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
Java HashMap containsKey returns false for existing object
提问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, containsKey
method returns false
.equals
and hashCode
methods are implemented, but the key is not found.
When debugging a piece of code:
但是,当尝试检查键是否存在时,containsKey
方法返回false
。equals
和hashCode
方法都实现了,但是没有找到关键。
调试一段代码时:
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 containsKey
cares 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 containsKey
method, however, goes like this:
containsKey
然而,该方法是这样的:
keyToFind
has ahashCode() == 0
, so let me look in bucket 0 (and onlythere). Oh, it's empty -- returnfalse.
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 int
comparison). But it also means that changing the key -- which will not change the stored e.key
field -- will break the map.
这是一个很好的优化,因为这意味着碰巧碰撞到同一个桶中的具有不同 hashCode 的键将被视为非常便宜的不相等(只是一个int
比较)。但这也意味着更改键(不会更改存储的e.key
字段)会破坏映射。
回答by Andremoniy
Here is SSCCE
for your issue bellow. It works like a charm and it couldn't be else, because your hashCode
and equals
methods seem to be autogenerated by IDE and they look fine.
以下是SSCCE
您的问题。它就像一个魅力,它不可能是其他的,因为您的hashCode
和equals
方法似乎是由 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 fields
object or bean
object. 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 return
statement 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 针对键集中的每个元素检查搜索键上的两件事: hashCode和equals;它按照这个顺序进行。
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 。