为什么包含Map的Key()只调用hashCode()?(Why containsKey() of Map only invoke hashCode()?)

编程入门 行业动态 更新时间:2024-10-09 22:15:30
为什么包含Map的Key()只调用hashCode()?(Why containsKey() of Map only invoke hashCode()?)

我阅读接口Map的JavaDoc,它说:

Collections Framework接口中的许多方法都是根据equals方法定义的。 例如, containsKey(Object key)方法的规范说:“当且仅当此映射包含密钥k的映射以使(key==null ? k==null : key.equals(k)) 。 本规范不应被解释为暗示使用非null参数key调用Map.containsKey将导致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()不被调用?

I read interface Map's JavaDoc and it says this:

Many methods in Collections Framework interfaces are defined in terms of the equals method. For example, the specification for the containsKey(Object key) method says: "returns true if and only if this map contains a mapping for a key k such that (key==null ? k==null : key.equals(k))." This specification should not be construed to imply that invoking Map.containsKey with a non-null argument key will cause key.equals(k) to be invoked for any key k. Implementations are free to implement optimizations whereby the equals invocation is avoided, for example, by first comparing the hash codes of the two keys. (The Object.hashCode() specification guarantees that two objects with unequal hash codes cannot be equal.)

My understanding is when containsKey() is invoked, both hashCode() and equals() wil be called, therefore I wrote my own code to test it.

HappyDay class will be stored as a key in HashMap, I override hashCode() and equals() methods, and add System.out.println("invoking hashCode()" + this.happyHour); and System.out.println("invoking equals()"); to check whether the method is invoked or not.

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

The Main class is to put two HappyDay instances into HashMap, after insertion(put() method), I invoke hm.containsKey(hd2), as I quoted from JavaDoc, it should call hashCode() first and then call equals(), but the output is

invoking hashCode()1 //call put() invoking hashCode()2 //call put() invoking hashCode()2 //call containsKey() found

I am expecting there's another output line should be invoking equals(), can anyone explain this for me why equals() is not called?

最满意答案

哈希映射首先通过==检查密钥相等性; 只有在失败的情况下才会继续进行equals检查

现在,您将hd2作为关键字放入映射中,然后您将使用同一个对象作为参数来检查containsKey ,所以==测试通过并且equals从不被调用。

检查映射是否包含给定键归结为检查getEntry(key)返回null 。 让我们看看来源 :

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测试,这就是发生在这里的事情。

这可能被实现,因为它是跳过潜在的昂贵的equals方法(例如, String#equals必须检查给定字符串的每个字符)。 equals合同还声明,如果o1 == o2 , o1.equals(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 。 这是有道理的,因为我们使用新的但平等的对象调用containsKey ,所以==测试返回false并执行equals测试。

Hash map first checks key equality via ==; only if that fails does it carry on to check with equals.

Now, you put hd2 into the map as a key, then you are checking containsKey using the same object as an argument, so the == test passes and equals is never called.

Checking if a map contains a given key comes down to checking if getEntry(key) returns null. Let's 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 }

On line 367, we can see that an == test is performed before a equals test. The short-circuiting of || completely skips the equals test if == passes, which is what is happening here.

This is probably implemented as it is to skip potentially expensive equals methods (e.g. String#equals which has to check each character of the given string). The equals contract also states that o1.equals(o2) should be true if o1 == o2, so this is a valid optimization.


Let's do a sanity check by modifying your code slightly:

if (hm.containsKey(new HappyDay(2))) { System.out.println("found"); } else { System.out.println("not exist"); }

Now the output is:

invoking hashCode()1 invoking hashCode()2 invoking hashCode()2 invoking equals() found

Notice that equals is invoked. This makes sense because we are invoking containsKey with a new but equal object, so the == test returns false and the equals test is executed.

更多推荐

本文发布于:2023-08-06 10:30:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1448361.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:Key   Map   hashCode   invoke   containsKey

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!