Java HashMap 包含键吗?

6

我有以下代码

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class Person {
  private String name;
  private long birthTime;

  @Override
  public int hashCode() {
    return Objects.hash(name, birthTime);
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (!(obj instanceof Person)) {
      return false;
    }
    Person other = (Person) obj;
    return Objects.equals(name, other.name)
        && birthTime == other.birthTime;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public long getBirthTime() {
    return birthTime;
  }

  public void setBirthTime(long birthTime) {
    this.birthTime = birthTime;
  }

  public static Person person(String name, long time) {
    Person p = new Person();
    p.setName(name);
    p.setBirthTime(time);
    return p;
  }

  public static void main(String[] args) {
    Map<Person, Person> map = new HashMap<>();
    Person p = person("alice", 3);
    System.out.println("1. " + map.containsKey(p));

    map.put(p, p);
    System.out.println("2. " + map.containsKey(p));

    p.setName("charlie");
    System.out.println("3. " + map.containsKey(p));

    Person p2 = person("alice", 3);
    System.out.println("4. " + map.containsKey(p2));

    Person p3 = person("charlie", 3);
    System.out.println("5. " + map.containsKey(p3));
  }
}

我希望输出结果为false、true、true、false和true。然而,实际输出为false、true、false、false、false。 我想知道第3和第5个情况为什么输出是false。HashMap containsKey的行为是什么? 即使Key对象存在于Map中,为什么输出还是false?Person类重写了equals和hashcode方法。

1
我不认为这里有什么多余的内容,除此之外我还需要添加很多冗长的细节因为SO并不允许我发布比代码更短的内容。如果你只是展示了代码而没有文字说明,那么问题就会变得难以理解。尽管你的代码可以更简短,只需要进行一个检查(或者可能是一个期望结果和一个非预期结果)而不是五个。 - Jon Skeet
1
HashMap中用作键的类应该是不可变的,出于这个原因。 - tgdavies
3个回答

5
以下语句会破坏您的地图:
p.setName("charlie");

当您更改变量phashCode()时,它将导致与其hashCode()匹配的bin中不再定位该变量所引用的键。

如果更改已在Map中存在的键的状态会影响hashCode()equals()的结果,则不应更改该键的状态。

p.setName("charlie");
System.out.println("3. " + map.containsKey(p));

返回 false,因为名字为"charlie"的Person实例与名字为"alice"的Person实例不映射到同一个垃圾桶中。因此,containsKey()在匹配名称为"charlie"的垃圾桶中搜索p,但没有找到。

Person p2 = person("alice", 3);
System.out.println("4. " + map.containsKey(p2));

返回 false,因为p2p不相等(它们具有不同的名称)。

Person p3 = person("charlie", 3);
System.out.println("5. " + map.containsKey(p3));

containsKey()返回false,因为键p位于与名称“alice”匹配的箱中,即使其当前名称为“charlie”,因此containsKey()在错误的箱中搜索它,无法找到。


啊,谢谢。我忽略了hashCode会因为Key值的改变而指向不同的桶。 - Tech Enthusiast
1
@科技爱好者:它是否解析到不同的桶并不重要 - 我肯定期望地图在调用equals之前会检查相等的哈希码(存储和“正在查找的键”)。即使只有一个桶,如果哈希码不同,它也无法被找到。 - Jon Skeet

3
在将对象添加为HashMap的键后,您修改了该对象,使其哈希码发生了变化。这就像是把你的联系方式给某人,搬家后仍然期望他们能够找到你。
当您将键添加到映射中时,它会存储哈希码。当您尝试查找键时,映射会要求查找的键的哈希码,并有效地查找具有相同存储哈希码的任何条目。由于“新”哈希码与“旧”哈希码不匹配,它无法找到任何候选项以使用equals检查。
基本上,您不应在将对象用作Map中的键之后修改影响哈希码或相等性的任何内容。

0
为了更详细地说明Eran的答案,我已经检查了一些HashMap的源代码。
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    ...
        tab[i] = newNode(hash, key, value, null);
    ...
}

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

在第三种情况中,即使您将键的名称更改为“Charlie”,“node”中键的哈希值仍保持不变。这就是为什么它返回false的原因。看起来您永远不应该更改对象键,因为这会导致哈希(key)不匹配而破坏映射。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接