考虑示例代码:
class EmployeeClass {
int id;
public EmployeeClass(int eid) {
this.id=eid;
}
@Override
public int hashCode(){
return this.id;
}
@Override
public boolean equals(Object o){
return true;
}
}
public class HashcodeAndEquals {
public static void main(String[] args) {
Map map=new HashMap();
EmployeeClass e1=new EmployeeClass(1);
map.put(e1, "Employee 1"); // line 1
EmployeeClass e2=new EmployeeClass(2);
map.put(e2, "Employee 2");
EmployeeClass e3=new EmployeeClass(3);
EmployeeClass e4=new EmployeeClass(1); // line 2
EmployeeClass e5=new EmployeeClass(1);
map.put(e5, "Employee 5"); // line 3
System.out.println("e1 -> "+map.get(e1));
System.out.println("e2 -> "+map.get(e2));
System.out.println("e3 -> "+map.get(e3));
System.out.println("e4 -> "+map.get(e4)); // line 4
System.out.println("e5 -> "+map.get(e5));
}
}
输出:
e1 -> Employee 5
e2 -> Employee 2
e3 -> null
e4 -> Employee 5
e5 -> Employee 5
第1行运行后,第3行覆盖e1
的值,但我的equals方法仅返回true。同样在第4行,我们得到e4
的值,即使equals
方法只返回true。由于equals
方法没有比较只返回true,因此put和get的工作原理如何。幕后发生了什么?
答案 0 :(得分:0)
我猜你是否感到惊讶,并非所有人都是员工5?等于总是返回true,所以e5应该覆盖所有这些,对吧?
HashMap使用"存储桶"在幕后。让我们说只有2,简单来说就是这样。一个真正的HashMap还有更多。
当您将对象放入HashMap时,它首先查看hashCode。 (这就是为什么它被称为HashMap。)基于hashCode,它选择一个存储桶来存储它。对于e1
,hashCode是1,所以它会选择存储桶1。对于e2
,它是2,因此进入存储桶2. e3
的hashCode为3,但只有2个存储桶,所以它将进入存储桶3模数2,即{1} e4
因同一原因进入存储桶2,e5
又返回1。
请注意,我极其简化;实际上这个过程有点复杂,但这应该足以解释事情。
所以我们有以下桶:
bucket 1 | bucket 2
e5 | e4
e3 | e2
e1 |
现在是时候检索价值了。当我检索e1
时,hashCode仍为1,因此它在存储桶1中查找。在那里,它选择equals
e1
的第一个对象,即e5
:你输入的最后一个对象,显然是第一个出来的对象。 e2
指向存储区2,因此在此示例中,您将得到e4
。
在您的情况下,您实际上从未将e3
和e4
放入HashMap。但由于e3
的hashCode指向存储桶1,并且它等于e5
,因此当您查找e3
时,您仍然可以获得e5
虽然它实际上并不存在。
正如我所说,这是对事物的极端简化。在真正的HashMap中,有超过2个桶。因此,在您的情况下,e3
有一个指向空桶的哈希码,而e1
,e4
和e5
的hashCodes显然都指向同一个存储桶。< / p>
这就是为什么在编写hashCode时给它一个好的&#34;分发&#34;重要的原因。如果你总是返回42,那么所有对象总是会进入同一个桶,这会把你的HashMap变成一个非常笨拙的ArrayList。您将失去HashMap为您提供的性能优势。