需要帮助来理解 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
来更改 person1
的 hashCode()
值时,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 有时,当您知道问题的答案时,您会忘记查找重复的问题,并在任何人回复之前就直接回答问题 - 这就是在这种情况下发生了什么。下次要小心:-)
假设我有一个人 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
来更改 person1
的 hashCode()
值时,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 有时,当您知道问题的答案时,您会忘记查找重复的问题,并在任何人回复之前就直接回答问题 - 这就是在这种情况下发生了什么。下次要小心:-)