我阅读了Map的JavaDoc接口,它说:
Collections Framework接口中的许多方法都是根据equals定义的 方法。例如,
containsKey(Object key)
方法的规范说: "当且仅当此映射包含键k
的映射时才返回true(key==null ? k==null : key.equals(k))
&#34。不应解释该说明书 暗示使用非Map.containsKey
参数null
调用key
将会 导致为任何键key.equals(k)
调用k
。实现是免费的 实现优化,从而避免equals
调用, 例如,首先比较两个密钥的哈希码。 (Object.hashCode()
规范保证两个对象具有不相等 哈希码不能相等。)
我的理解是当调用containsKey()
时,都会调用hashCode()和equals(),因此我编写了自己的代码来测试它。
HappyDay类将作为键存储在HashMap中,我重写hashCode()和equals()方法,并添加System.out.println("invoking hashCode()" + this.happyHour);
和System.out.println("invoking equals()");
来检查方法是否被调用。
public class HappyDay {
private final int happyHour;
public HappyDay(int hh) {
this.happyHour = hh;
}
public int getHappyHour() {
return this.happyHour;
}
@Override
public boolean equals(Object o) {
System.out.println("invoking equals()");
if (o == null) {return false;}
if (o == this) {return true;}
//this is an easy overridden, if happy hour equal, objects will be equal.
if (o instanceof HappyDay) {
HappyDay other = (HappyDay) o;
int otherHappyHour = other.getHappyHour();
if (this.happyHour == otherHappyHour) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
System.out.println("invoking hashCode()" + this.happyHour);
int hash = 7;
hash = hash + this.happyHour;
return hash;
}
}
public class Main {
public static void main(String[] args) {
Map<HappyDay,String> hm = new HashMap<>();
HappyDay hd1 = new HappyDay(1);
HappyDay hd2 = new HappyDay(2);
hm.put(hd1, "hd1");
hm.put(hd2, "hd2");
if(hm.containsKey(hd2)){
System.out.println("found");
}else{
System.out.println("not exist");
}
}
}
Main类是将两个HappyDay实例放入HashMap,插入(put()方法后),我调用hm.containsKey(hd2)
,正如我从JavaDoc引用的那样,它应首先调用hashCode()然后调用equals( ),但输出
invoking hashCode()1 //call put()
invoking hashCode()2 //call put()
invoking hashCode()2 //call containsKey()
found
我期待另一个输出行应该是invoking equals()
,有人可以为我解释为什么不调用equals()吗?
答案 0 :(得分:6)
哈希映射首先通过==
检查密钥相等性; 只有在失败时才会继续检查equals
。
现在,您将hd2
作为键添加到地图中,然后使用相同对象作为参数检查containsKey
,因此==
测试通过并且永远不会调用equals
。
检查地图是否包含给定密钥,直至检查getEntry(key)
是否返回null
。我们look at the source:
360 final Entry<K,V> getEntry(Object key) {
361 int hash = (key == null) ? 0 : hash(key.hashCode());
362 for (Entry<K,V> e = table[indexFor(hash, table.length)];
363 e != null;
364 e = e.next) {
365 Object k;
366 if (e.hash == hash &&
367 ((k = e.key) == key || (key != null && key.equals(k))))
368 return e;
369 }
370 return null;
371 }
在第367行,我们可以看到在 ==
测试之前执行了equals
测试。如果||
通过,则equals
的{{3}}会完全跳过==
测试,这就是这里发生的事情。
这可能是实现的,因为它是跳过可能昂贵的equals
方法(例如String#equals
必须检查给定字符串的每个字符)。 short-circuiting还指出如果o1.equals(o2)
o1 == o2
应该为真,那么这是有效的优化。
让我们稍微修改你的代码来进行健全性检查:
if (hm.containsKey(new HappyDay(2))) {
System.out.println("found");
} else {
System.out.println("not exist");
}
现在输出是:
invoking hashCode()1 invoking hashCode()2 invoking hashCode()2 invoking equals() found
请注意,equals
已被调用。这是有道理的,因为我们使用 new 但是相同的对象调用containsKey
,因此==
测试返回false并执行equals
测试。
答案 1 :(得分:4)
The source code for HashMap
告诉我们发生了什么。
containsKey
方法调用getEntry
:
360 final Entry<K,V> More ...getEntry(Object key) {
361 int hash = (key == null) ? 0 : hash(key.hashCode());
362 for (Entry<K,V> e = table[indexFor(hash, table.length)];
363 e != null;
364 e = e.next) {
365 Object k;
366 if (e.hash == hash &&
367 ((k = e.key) == key || (key != null && key.equals(k))))
368 return e;
369 }
370 return null;
371 }
如果哈希值匹配,则首先将key
与==
进行比较。由于您传入原始hd2
对象,==
运算符返回true
(它是同一个对象),并且短路||
运算符从不评估右侧,并且未调用equals
。
如果您要制作另一个HappyDay
,同时传递2
,那么==
将会产生false
,并且会调用equals
。
至于为什么==
首先存在,我不得不猜测,首先执行快速==
会更有效率,而且只会对{{{{{{ 1}}如果它们不是同一个对象。