Java HashMap containsKey为现有对象返回false

aga*_*gad 30 java hashmap containskey

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

    private Map<T, U> fields = Collections.synchronizedMap(new HashMap<T, U>());
Run Code Online (Sandbox Code Playgroud)

但是,当试图检查密钥的存在时,containsKey方法返回false.
equalshashCode方法已实现,但找不到密钥.
调试一段代码时:

    return fields.containsKey(bean) && fields.get(bean).isChecked();
Run Code Online (Sandbox Code Playgroud)

我有:

   bean.hashCode() = 1979946475 
   fields.keySet().iterator().next().hashCode() = 1979946475    
   bean.equals(fields.keySet().iterator().next())= true 
   fields.keySet().iterator().next().equals(bean) = true
Run Code Online (Sandbox Code Playgroud)

fields.containsKey(bean) = false
Run Code Online (Sandbox Code Playgroud)

什么可能导致这种奇怪的行为?

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


}
Run Code Online (Sandbox Code Playgroud)

Arn*_*lle 25

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

编辑:我在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;
  }
}
Run Code Online (Sandbox Code Playgroud)

和测试:

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));
}
Run Code Online (Sandbox Code Playgroud)

输出:

true
false
Run Code Online (Sandbox Code Playgroud)

注意:如果你不重写hashcode(),那么你只会得到真实

  • 这是我所期望的。但我不会更改 hashCode (equals) 中使用的字段 (2认同)

ysh*_*vit 10

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

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

[0: empty]  [1: yourKeyValue]
Run Code Online (Sandbox Code Playgroud)

迭代器是这样的:

  • 迭代桶0中的所有元素:没有
  • 迭代桶1中的所有元素:只是一个 yourKeyValue

containsKey但是,该方法如下:

  • keyToFind有一个hashCode() == 0,所以让我看看桶0(只有那里).哦,它是空的 - 回归false.

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

((k = e.key) == key || (key != null && key.equals(k))))
Run Code Online (Sandbox Code Playgroud)

这是一个很好的优化,因为它意味着碰巧碰撞到同一个桶中的具有不同hashCodes的密钥将被视为非等价非常便宜(只是int比较).但这也意味着更改密钥 - 这不会改变存储的e.key字段 - 将破坏地图.


Ped*_*ina 5

调试java源代码我意识到方法containsKey检查搜索键上的两个东西对着密钥集中的每个元素: hashCodeequals ; 它按顺序执行.

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

您必须确保两种方法 - 可能必须覆盖它们 - 对于您定义的标准,评估为true.