我有一个用于存储对象的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
什么可能导致这种奇怪的行为?
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;
}
}
答案 0 :(得分:23)
将密码插入地图后,您不得修改密钥。
编辑:我在Map中找到了javadoc的摘录:
注意:如果将可变对象用作映射键,则必须非常小心。如果在对象是地图中的键时,以影响等于比较的方式更改对象的值,则不指定映射的行为。
使用简单包装类的示例:
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()那么你只会得到真实的
答案 1 :(得分:10)
正如Arnaud Denoyelle所指出的那样,修改密钥会产生这种影响。 原因是containsKey
关心哈希映射中的密钥桶,而迭代器则不然。如果地图中的第一个键 - 忽略存储桶 - 恰好是您想要的那个,那么您可以获得您所看到的行为。如果地图中只有一个条目,这当然是有保障的。
想象一个简单的双桶地图:
[0: empty] [1: yourKeyValue]
迭代器是这样的:
yourKeyValue
containsKey
方法如下:
keyToFind
有一个hashCode() == 0
,所以让我看一下0(和只那里)。哦,它是空的 - 返回false.
事实上,即使密钥停留在同一个存储桶中,您仍然会出现此问题!如果您查看HashMap
的实现,您会看到每个键值对都与一起存储在键的哈希码中。当地图要针对传入的密钥检查存储的密钥时,它使用both this hashCode and the key's equals
:
((k = e.key) == key || (key != null && key.equals(k))))
这是一个很好的优化,因为这意味着碰巧碰到同一个桶的具有不同hashCodes的密钥将被视为非等价非常便宜(只是int
比较)。但这也意味着更改密钥 - 不会更改存储的e.key
字段 - 将破坏地图。
答案 2 :(得分:5)
调试java源代码我意识到方法containsKey检查搜索到的键上的两个东西对着键集中的每个元素: hashCode 和等于;它按顺序执行。
这意味着如果obj1.hashCode() != obj2.hashCode()
,则返回false(不评估obj1.equals(obj2)。但是,如果obj1.hashCode() == obj2.hashCode()
,则返回obj1.equals(obj2)
你必须确定两种方法 - 你必须覆盖它们 - 对你定义的标准评估为真。
答案 3 :(得分:1)
以下是您SSCCE
的问题。它就像一个魅力,它不可能是其他的,因为你的hashCode
和equals
方法似乎是由IDE自动生成的,它们看起来很好。
因此,关键字为when debugging
。调试本身可能会损害您的数据。例如,在调试窗口的某处,您可以设置更改fields
对象或bean
对象的表达式。之后,你的其他表达会给你意想不到的结果。
尝试在方法中添加所有这些检查,从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;
}
}