先以一段代码开始这篇blog。

01 public class Name {
02  
03   private String first; //first name
04   private String last;  //last name
05  
06   public String getFirst() {
07     return first;
08   }
09  
10   public void setFirst(String first) {
11     this.first = first;
12   }
13  
14   public String getLast() {
15     return last;
16   }
17  
18   public void setLast(String last) {
19     this.last = last;
20   }
21  
22   public Name(String first, String last) {
23     this.first = first;
24     this.last = last;
25   }
26  
27   @Override
28   public boolean equals(Object object) {
29     Name name = (Name) object;
30  
31     return first.equals(name.getFirst()) && last.equals(name.getLast());
32   }
33  
34   public static void main(String[] args) {
35     Map<Name, String> map = new HashMap<Name, String>();
36     map.put(new Name("mali", "sb"), "yes");
37      
38     System.out.println("is the key existed? ture or false? -> "
39         + map.containsKey(new Name("mali", "sb")));
40   }
41  
42 }

那输出结果是什么呢?类似这样的题目总能遇到,以前不知道有什么好考的,弱智?自己动手尝试了一次,发现结果不是自己想象的那样。本篇就用来揭示HashMap的equals与hashCode中你不知道的秘密。结果如下:

is the key existed? ture or false? -> false

对的,结果就是false,我很不理解为什么这样,已经重写了equals函数了啊!当时真心不服气,就在equals函数里面打了断点,然后更让我难以置信的事情发生了,断点处没有停。非常困惑,不过还好,jdk的源码在手上,去查了HashMap中containsKey函数的源码。源码结构如下图:

从图中可以看出,真正干活的是getEntry(Object key),重点看如下两行:

1 if (e.hash == hash &&
2                 ((k = e.key) == key || (key != null && key.equals(k))))
3     return e;

从if条件上看,是一个短路与,首先要判断两个对象的hash值是否相等。如果相等才进行后续的判断。或者换一个说法,在HashMap中只有两个对象的hash值相等的前提下才会执行equals方法的逻辑。关于这一点,有两个佐证。

在stackoverflow上找到一篇关于HashMap不执行equals方法的文章,回答中有明确给出这样的答案。Java HashMap.containsKey() doesn’t call equals()

自己编程验证。

在文章开头的基础上,做了点儿改进,输出两个对象的hash值,并且在equals方法中打印一行文字。如下:

01 public class Name {
02  
03   private String first; //first name
04   private String last;  //last name
05  
06   public String getFirst() {
07     return first;
08   }
09  
10   public void setFirst(String first) {
11     this.first = first;
12   }
13  
14   public String getLast() {
15     return last;
16   }
17  
18   public void setLast(String last) {
19     this.last = last;
20   }
21  
22   public Name(String first, String last) {
23     this.first = first;
24     this.last = last;
25   }
26  
27   @Override
28   public boolean equals(Object object) {
29     System.out.println("equals is running...");
30     Name name = (Name) object;
31  
32     return first.equals(name.getFirst()) && last.equals(name.getLast());
33   }
34  
35   public static void main(String[] args) {
36     Map<Name, String> map = new HashMap<Name, String>();
37     Name n1 = new Name("mali", "sb");
38     System.out.println("the hashCode of n1 : " + n1.hashCode());
39     map.put(n1, "yes");
40     Name n2 = new Name("mali", "sb");
41     System.out.println("the hashCode of n2 : " + n2.hashCode());
42     System.out.println("is the key existed? ture or false? -> "
43         + map.containsKey(n2));
44   }
45  
46 }

结果:

the hashCode of n1 : 1690552137

the hashCode of n2 : 1901116749

is the key existed? ture or false? -> false

从执行结果可以看出1、两个对象的hash值是不相同的;2、equals方法确实也没有执行。

再次对代码进行改进,加入重写的hashCode方法,如下,看看这次的结果会是怎样。

01 public class Name {
02  
03   private String first; //first name
04   private String last;  //last name
05  
06   public String getFirst() {
07     return first;
08   }
09  
10   public void setFirst(String first) {
11     this.first = first;
12   }
13  
14   public String getLast() {
15     return last;
16   }
17  
18   public void setLast(String last) {
19     this.last = last;
20   }
21  
22   public Name(String first, String last) {
23     this.first = first;
24     this.last = last;
25   }
26  
27   @Override
28   public boolean equals(Object object) {
29     System.out.println("equals is running...");
30     Name name = (Name) object;
31  
32     return first.equals(name.getFirst()) && last.equals(name.getLast());
33   }
34  
35   public int hashCode() {
36     System.out.println("hashCode is running...");
37     return first.hashCode() + last.hashCode();
38   }
39  
40   public static void main(String[] args) {
41     Map<Name, String> map = new HashMap<Name, String>();
42     Name n1 = new Name("mali", "sb");
43     System.out.println("the hashCode of n1 : " + n1.hashCode());
44     map.put(n1, "yes");
45     Name n2 = new Name("mali", "sb");
46     System.out.println("the hashCode of n2 : " + n2.hashCode());
47     System.out.println("is the key existed? ture or false? -> "
48         + map.containsKey(n2));
49   }
50  
51 }

结果:

hashCode is running...

the hashCode of n1 : 3347552

hashCode is running...

hashCode is running...

the hashCode of n2 : 3347552

hashCode is running...

equals is running...

is the key existed? ture or false? -> true

同样从结果中可以看出:在hash值相等的情况下,equals方法也执行了,HashMap的containsKey方法也像预想的那样起作用了。

结论:

在使用HashSet(contains也是调用HashMap中的方法)、HashMap等集合时,如果用到contains系列方法时,记得需同时重写equals与hashCode方法。

HashMap之equals和hashCode小陷阱的更多相关文章

  1. Java中equals,hashcode

         在Java语言中,Object对象中包含一个equals和hashCode方法,其中hashCode方法是由JVM本地代码(native code)实现的,返回值是一个有符号的32位整数,对 ...

  2. [转载] HashMap的工作原理-hashcode和equals的区别

    目录 前言 为什么需要使用Hashcode,可以从Java集合的常用需求来描述: 更深入的介绍 先来些简单的问题 HashMap的0.75负载因子 总结 我在网上看到的这篇文章,介绍的很不错,但是我看 ...

  3. == 和 equals,equals 与 hashcode,HashSet 和 HashMap,HashMap 和 Hashtable

    一:== 和 equals == 比较引用的地址equals 比较引用的内容 (Object 类本身除外) String obj1 = new String("xyz"); Str ...

  4. 堆、栈、内存分配、==、equals、hashcode详解(转载)

    问题的引入: 问题一:String str1 = "abc";String str2 = "abc";System.out.println(str1==str2 ...

  5. 如何在Java中避免equals方法的隐藏陷阱

    摘要 本文描述重载equals方法的技术,这种技术即使是具现类的子类增加了字段也能保证equal语义的正确性. 在<Effective Java>的第8项中,Josh Bloch描述了当继 ...

  6. Java == ,equals 和 hashcode 的区别和联系(阿里面试)

    今天阿里的人问我 equals 与hashcode的区别,我答不上来, 仔细查了一下,做了总结: (1) == 这是Java 比较内存地址,就是内存中的对象: java中的==是比较两个对象在JVM中 ...

  7. Object之equals和hashCode

    译者注 :你可能会觉得Java很简单,Object的equals实现也会非常简单,但是事实并不是你想象的这样,耐心的读完本文,你会发现你对Java了解的是如此的少.如果这篇文章是一份Java程序员的入 ...

  8. 关于重写equals()和hashCode()的思考

    最近这几天一直对equals()和hashCode()的事搞不清楚,云里雾里的. 为什么重写equals(),我知道. 但是为什么要两个都要重写呢,我就有点迷糊了,所以趁现在思考清楚后记录一下. 起因 ...

  9. 如何编写出高质量的 equals 和 hashcode 方法?

    什么是 equals 和 hashcode 方法? 这要从 Object 类开始说起,我们知道 Object 类是 Java 的超类,每个类都直接或者间接的继承了 Object 类,在 Object ...

随机推荐

  1. excel VBA 将文本数值转换为数字格式(单元格中数据左上角是绿三角,鼠标点上有叹号标示)

    Range("A6").SelectSelection.CopyRange("A10:A60").SelectRange(Selection, Selectio ...

  2. android/libs/libammsdk.jar" already exists! 解决方法

    Error: Uh oh!"/work/2016/fengkongbao/.meteor/local/cordova-build/platforms/android/libs/libamms ...

  3. 《Scrum实战》第4次课【全职的Scrum Master】作业汇总

    1组: 孟帅 http://www.cnblogs.com/mengshuai1982/p/7375008.html 3组: 张亚辉 http://www.jianshu.com/p/df9eee08 ...

  4. 【Triangle 】cpp

    题目: Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjace ...

  5. putty的基本使用

    1.输入你要连接的目标的IP地址,输入你要给它取的名字,点击保存 2.选中你保存的会话,点击打开,即可打开会话 3.输入你连接的目标的用户账号,回车,再输入密码.即可正常使用. 4.编码的设置 在出现 ...

  6. 最简单的RSA及其几个网站和工具

    最简单的形式 给你公钥和一个密文. flag.enc就是密文,我们用记事本是看不出什么的,其实也不用看,因为后边的解密是直接用脚本读取文件的,只需要知道这是密文. pub.pem就是公钥,用记事本打开 ...

  7. Wordpress Uncaught TypeError: b(...).not(...).filter(...).mediaelementplayer is not a function

    Wordpress 插件页面报错如下图: 原因及解决方法: 引入了两次 jquery.js 或 jquery.js 定义的变量导致报错,删除在插件页面自己引入的 jquery,js 即可解决报错. 大 ...

  8. js 遍历对象属性(for in、Object.keys、Object.getOwnProperty) 以及高效地输出 js 数组

    js中几种遍历对象的方法,包括for in.Object.keys.Object.getOwnProperty,它们在使用场景方面各有不同. for in 主要用于遍历对象的可枚举属性,包括自有属性. ...

  9. PostgreSQL 配置内存参数

    对于任何数据库软件,内存配置项都是很重要的配置项.在 PostgreSQL 主要有以下几个内存配置参数. shared_buffers: integer 类型,设置数据库服务器将使用的共享内存缓冲区数 ...

  10. PostgreSQL 如何优化索引效率

    使用 gin() 创建全文索引后,虽然有走索引,但是当结果集很大时,查询效率还是很底下, SELECT keyword,avg_mon_search,competition,impressions,c ...