[英]Java HashMap containsKey returns false for existing object
我有一個用於存儲對象的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;
}
}
將密鑰插入地圖后,您不得修改密鑰。
編輯:我在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(),那么你只會得到真實
正如Arnaud Denoyelle所指出的那樣,修改密鑰會產生這種效果。 原因是containsKey
關心哈希映射中的密鑰桶,而迭代器則不然。 如果地圖中的第一個鍵 - 忽略存儲桶 - 恰好是您想要的那個,那么您可以獲得您所看到的行為。 如果地圖中只有一個條目,這當然是有保證的。
想象一個簡單的雙桶地圖:
[0: empty] [1: yourKeyValue]
迭代器是這樣的:
yourKeyValue
但是, containsKey
方法如下所示:
keyToFind
有一個hashCode() == 0
,所以讓我看看桶0( 只有那里)。 哦,它是空的 - 返回false.
事實上,即使密鑰停留在同一個桶中,您仍然會遇到此問題! 如果查看HashMap
的實現,您將看到每個鍵值對與鍵的哈希碼一起存儲。 當地圖想要針對傳入的密鑰檢查存儲的密鑰時,它使用此hashCode和密鑰的equals
:
((k = e.key) == key || (key != null && key.equals(k))))
這是一個很好的優化,因為這意味着碰巧碰到同一個桶的具有不同hashCodes的密鑰將被視為非等價非常便宜(只是一個int
比較)。 但這也意味着更改密鑰 - 不會更改存儲的e.key
字段 - 將破壞地圖。
調試java源代碼我意識到方法containsKey檢查搜索鍵上的兩個東西對着密鑰集中的每個元素: hashCode和equals ; 它按順序執行。
這意味着如果obj1.hashCode() != obj2.hashCode()
,它返回false(不評估obj1.equals(obj2)。但是,如果obj1.hashCode() == obj2.hashCode()
,則返回obj1.equals(obj2)
您必須確保兩種方法 - 可能必須覆蓋它們 - 對於您定義的標准,評估為true。
以下是您的問題的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;
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.