需要帮助来理解 HashMap 的行为

Need help to understand behaviour of HashMap

假设我有一个人 class 并且平等基于 id 属性。下面是 Person class -

的实现
class Person {

    private int id;
    private String firstName;

    public Person(int id, String firstName) {
        super();
        this.id = id;
        this.firstName = firstName;
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public int hashCode() {
        return this.id;
    }

    public boolean equals(Object obj) {
        return ((Person) obj).getId() == this.id;
    }
}

我正在使用 Person class 作为 HashMap 的键。现在看下面的代码 -

import java.util.HashMap;

public class TestReport {
    public static void main(String[] args) {

        Person person1 = new Person(1, "Person 1");
        Person person2 = new Person(2, "Person 2");

        HashMap<Person, String> testMap = new HashMap<Person, String>();
        testMap.put(person1, "Person 1");
        testMap.put(person2, "Person 2");

        person1.setId(2);

        System.out.println(testMap.get(person1));
        System.out.println(testMap.get(person2));

    }
}

注意,虽然我们在HashMap中添加了两个不同的person对象作为key,但是后来我们将person1对象的id改为2,使两个person对象相等。

现在,我得到的输出为 -

Person 2
Person 2

我可以看到 HashMap 中有两个带有数据的键值对:"person1/Person 1" 和 "person2/Person 2",但我仍然总是得到 "Person 2" 作为输出,我永远无法访问值 "Person 1"。另请注意,我们在 HashMap 中有重复键。

看了源码我能理解这个行为,但是好像没有问题?我们可以采取一些预防措施来预防吗?

发生这种情况是因为您的 equals() 方法仅使用哈希码。您应该比较所有人员字段,例如 firstName.

这完全取决于 hashCode() 值如何被 HashMap 使用。

虽然要求两个相等的对象具有相同的hash code,但反过来不一定成立。两个不相等的对象可以具有相同的哈希码(因为 int 只有有限的一组可能值)。

每次您将对象放入 HashMap 时,它会将对象存储在由键 hashCode() 标识的存储桶中。因此,根据 hashCode() 的实施方式,您应该在各个存储桶中公平分配条目。

现在,当您尝试检索一个值时,HashMap 将识别给定 key 所在的桶,然后遍历该桶中的所有键以选择条目给定密钥 - 在这个阶段它将使用 equals() 方法来识别条目。

在你的情况下,person1 坐在 bucket 1 中,person2 坐在 bucket 2 中。

但是,当您通过更新其 id 来更改 person1hashCode() 值时,HashMap 并不知道此更改。稍后,当您使用 person1 作为键查找条目时,HashMap 认为它应该出现在 bucket 2 中(因为 person1.hashCode() 现在是 2) ,然后当它使用 equals 方法迭代 bucket 2 时,它找到了 person2 的条目并认为它是您感兴趣的对象 equals案例也基于 id 属性。

上面的解释在看implementation of HashMap#get方法时就很明显了,如下所示:

public V get(Object key) {
    if (key == null)
        return getForNullKey();
    int hash = hash(key.hashCode());
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
            return e.value;
    }
    return null;
}

PS 有时,当您知道问题的答案时,您会忘记查找重复的问题,并在任何人回复之前就直接回答问题 - 这就是在这种情况下发生了什么。下次要小心:-)