Java HashMap containsKey为现有对象返回false

Java HashMap containsKey为现有对象返回false,java,hashmap,containskey,Java,Hashmap,Containskey,我有一个用于存储对象的HashMap: private Map<T, U> fields = Collections.synchronizedMap(new HashMap<T, U>()); 我有: bean.hashCode() = 1979946475 fields.keySet().iterator().next().hashCode() = 1979946475 bean.equals(fields.keySet().ite

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

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

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

什么会导致这种奇怪的行为

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


}
公共类地址扩展DtoImpl实现可序列化{
@凌驾
公共int hashCode(){
最终整数素数=31;
int结果=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();
返回结果;
}
@凌驾
公共布尔等于(对象obj){
if(this==obj)
返回true;
if(obj==null)
返回false;
如果(getClass()!=obj.getClass())
返回false;
地址其他=(地址)obj;
如果(!StringUtils.trimToEmpty(street).equals(StringUtils.trimToEmpty(other.getStreet()))
返回false;
如果(!StringUtils.trimToEmpty(town).equals(StringUtils.trimToEmpty(other.getTown()))
返回false;
如果(!StringUtils.trimToEmpty(code).equals(StringUtils.trimToEmpty(other.getCode()))
返回false;
如果(国家==null){
if(other.country!=null)
返回false;
}如果(!country.equals(other.country))
返回false;
返回true;
}
}

将钥匙插入地图后,不得修改钥匙

编辑:我在以下位置找到了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));
}

注意:如果不重写hashcode(),则只会得到true,正如Arnaud Denoyelle指出的那样,修改一个键会产生这种效果。原因是
containsKey
关心散列映射中键的bucket,而迭代器不关心。如果地图中的第一个关键点——忽略桶——恰好是您想要的,那么您可以得到您看到的行为。如果地图上只有一个条目,这当然是有保证的

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

[0: empty]  [1: yourKeyValue]
迭代器如下所示:

  • 迭代bucket 0中的所有元素:没有
  • 迭代bucket 1中的所有元素:仅迭代一个
    yourKeyValue
然而,
containsKey
方法如下:

  • keyToFind
    有一个
    hashCode()==0
    ,所以让我看看bucket 0(并且只在那里)。哦,它是空的--返回
    false。
事实上,即使钥匙留在同一个桶里,你仍然会有这个问题!如果查看
HashMap
的实现,您将看到每个键值对都与键的哈希代码一起存储。当地图想要对照传入的密钥检查存储的密钥时,它使用:


这是一个很好的优化,因为这意味着碰巧碰撞到同一个bucket中的具有不同hashcode的键将被视为不相等的键(只是一个
int
比较)。但这也意味着更改密钥(不会更改存储的
e.key
字段)将破坏地图。

以下是
SSCCE
。它就像一个符咒,不可能是别的,因为您的
hashCode
equals
方法似乎是由IDE自动生成的,它们看起来很好

因此,调试时的关键字是
。调试本身可能会损害您的数据。例如,在调试窗口的某个地方,您可以设置表达式来更改
字段
对象或
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;
    }


}
import org.apache.commons.lang.StringUtils;
导入java.io.Serializable;
导入java.util.Collections;
导入java.util.HashMap;
导入java.util.Map;
公开课Q21600344{
公共静态void main(字符串[]args){
MapClass MapClass=新的MapClass();
mapClass.put(新地址(“a”、“b”、“c”、“d”),新的可检查(){
@凌驾
公共布尔值已检查(){
返回true;
}
});
System.out.println(mapClass.isChecked(新地址(“a”、“b”、“c”、“d”)));
}
}
接口可检查{
布尔值被选中();
}
类映射类{
私有映射字段=Collections.synchronizedMap(新的HashMap());
公共布尔值已检查(T bean){
返回fields.containsKey(bean)和&fields.get(bean.isChecked();
}
公开作废认沽权证(T,U){
字段。put(t,u);
}
}
类地址实现可序列化{
私家弦街;;
私人弦镇;
私有字符串码;
私人国家;
地址(字符串街道、字符串城镇、字符串代码、字符串国家){
这条街;
this.town=城镇;
this.code=代码;
这个国家=国家;
}
字符串getStreet(){
返回街;;
}
字符串getTown(){
返回城镇;
}
字符串getCode(){
返回码;
}
字符串getCountry(){
返回国;
}
@凌驾
公共int hashCode(){
最终整数素数=31;
int结果=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();
返回结果;
}
@凌驾
公共布尔等于(对象obj){
if(this==obj)
true
false
[0: empty]  [1: yourKeyValue]
((k = e.key) == key || (key != null && key.equals(k))))
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;
    }


}