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

我期望输出为假、真、真、假和真。但是,输出是假,真,假,假,假。

我正在寻找第 3 种和第 5 种情况的输出如何为假。HashMap containsKey 的行为是什么?

为什么即使 Key 对象存在于 Map 中,输出仍为 false。对于 Person 类,equals 和 hashcode 方法都被覆盖了。

4

3 回答 3

5

以下语句破坏了您的地图:

p.setName("charlie");

它会导致变量引用的键p不再位于与其匹配的 bin 中hashCode(),因为您正在更改其hashCode().

如果更改影响hashCode()or的结果,则永远不应更改已在 Map 中的键的状态equals()

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

false由于名称为“charlie”的实例未映射到与名称为“alice”Person的实例相同的 bin,因此返回。Person因此在与名称“charlie”匹配的 bin 中containsKey()搜索p,但没有找到它。

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

返回false因为p2不等于p(它们有不同的名称)。

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

返回false,因为密钥p位于与名称“alice”匹配的 bin 中,即使它的当前名称是“charlie”,因此containsKey()在错误的 bin 中搜索它,但没有找到它。

于 2020-07-13T07:01:02.943 回答
3

在将对象添加为 中的键后,您正在修改对象HashMap,以更改哈希码的方式。这就像给某人您的联系方式,搬家,然后仍然期望他们能够找到您。

当您向地图添加键时,它会存储哈希码。当您尝试查找密钥时,映射会询问您尝试查找的密钥的哈希码,并有效地找到具有相同存储哈希码的任何条目。由于“新”哈希码与“旧”哈希码不匹配,因此无法找到任何候选对象来检查equals.

基本上,在将对象用作映射中的键之后,您不应修改任何影响哈希码或相等性的内容。

于 2020-07-13T07:02:46.363 回答
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;
}

在第三种情况下,“节点”中键的哈希值保持不变,即使您将其名称更改为“查理”。这就是它返回假的原因。鉴于 hash(key) 不匹配会破坏映射,您似乎永远不应该更改对象键

于 2020-07-13T14:12:50.387 回答