JAVA基础-自问自答学hashCode和equals

前言

hashCodeequals常常在面试中会被问到,在工作中我们也有可能遇到要重写对象equals方法的情况,而且hashCode方法的设计思想值得我们学习,所以我们有必要去深入学习一下这两个方法。

下面我就以面试问答的形式学习我们的——hashcodeequals方法(源码分析基于JDK8)

问答内容

1.

问:hashCode方法有了解过吗?这个方法有什么用?

答:从JAVA官方对hashCode方法的说明定义(定义在示例代码中),我们可以得知hashCode的作用有如下几点:

  1. hashCode的存在主要用于查找的快捷性,如HashtableHashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的。

  2. 如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同。

  3. 如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点。

  4. 两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。

1.hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有  
例如内存中有这样的位置  
0  1  2  3  4  5  6  7    
而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,
如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。  
但如果用hashcode那就会使效率提高很多。  
我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,
然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,
9除8的余数为1,那么我们就把该类存在1这个位置,
如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。
这样,以后在查找该类时就可以通过ID除 8求余数直接找到存放的位置了。  
  
2.但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),
例如9除以8和17除以8的余数都是1,那么这是不是合法的,
回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。  
也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,
但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。  
那么。重写了equals(),为什么还要重写hashCode()呢?  
想想,你要在一个桶里找东西,你必须先要找到这个桶啊,
你不通过重写hashcode()来找到桶,光重写equals()有什么用啊  

上述回答转载于:Java中hashCode的作用 由于作者总结的太好,所以直接转载了

示例代码:

package java.lang;

public class Object {
·······

    /**
     * 返回该对象的哈希码值。
     * 支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能
     * {@link java.util.HashMap}.
     * <p>
     * hashCode 的常规协定是:
     * <ul>
     * <li>在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,
     *     必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。
     *     从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
     * <li>如果根据 equals(Object) 方法,两个对象是相等的,
     *     那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
     * <li>如果根据 equals(java.lang.Object) 方法,两个对象不相等,
     *     那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。
     *     但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
     * </ul>
     * <p>
     * 实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。
     * (这一般是通过将该对象的内部地址转换成一个整数来实现的,
     * 但是 JavaTM 编程语言不需要这种实现技巧。)
     *
     * @return  此对象的一个哈希码值。
     * @see     java.lang.Object#equals(java.lang.Object)
     * @see     java.lang.System#identityHashCode
     */
    public native int hashCode();
·······
}

2.

问:谈谈你对equals(Object obj)方法的理解,它和 == 操作符相比,有什么区别?

答:
A.== 操作符分为两种情况:

  • 比较基础类型(byte,short,int,long,float,double,char,boolean)时,比较的是值是否相等

  • 比较对象,比较的是对象在内存中的空间地址是否相等。

B.equals(Object obj)方法比较也分为两种情况:

  • 如果一个类没有重写equals(Object obj)方法,则等价于通过==比较两个对象,即比较的是对象在内存中的空间地址是否相等。

  • 如果重写了equals(Object obj)方法,则根据重写的方法内容去比较相等,返回true则相等,false则不相等。

3.

问:那如果要您去重写equals(Object obj)方法,您会怎么做?重写的过程需要注意什么?

答:我们在重写equals(Object obj)方法,需要遵守JAVA官方的通用约定(详细请看示例代码),约定简述:

  • 自反性:对于非 null 的对象 x,必须有 x.equals(x)=true;

  • 对称性:如果 x.equals(y)=true,那么 y.equals(x) 必须也为true;

  • 传递性:如果 x.equals(y)=true 而且 y.equals(z)=true,那么x.equals(z) 必须为true;

  • 对于非 null 的对象 x,一定有x.equals(null)=false

  • equals(Object obj)方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

根据上述约定,我们可以按如下步骤重写equals(Object obj)

1). 先使用 == 操作符判断两个对象的引用地址是否相同。
2). 使用instanceof来判断 两个对象的类型是否一致。
3). 如果类型相同,则把待比较参数转型,逐一比较两个对象内部的值是否一致,全部一致才返回true,否则返回false
4). 重写hashCode方法,确保相等的两个对象必须具有相等的哈希码。

  • 我们在重写一个类的hashCode方法时,最好是将所有用于相等性检查的字段都进行hashCode计算,最后将所有hashCode值相加,得出最终的hashCode,这样可以保证hashCode生成均匀,不容易产生碰撞。

常见数据类型hashcode计算方式如下(参考自JDK源码):

重要字段var的类型 hash运算
byte,short,int,char (int)var
long (int)(var ^ (var >>> 32))
float Float.floatToIntBits(var)
double long bits = Double.doubleToLongBits(var);分量 = (int)(bits ^ (bits >>> 32));
引用类型 (null == var ? 0 : var.hashCode())
hashCode计算-图片来自于《Effective Java》

示例代码:


    /**
     * 指示其他某个对象是否与此对象“相等”。
     * <p>
     * equals 方法在非空对象引用上实现相等关系:
     * <ul>
     * <li>自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
     *
     * <li>对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,
     * x.equals(y) 才应返回 true。
     *
     * <li>传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,
     * 并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
     *
     * <li>一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 
     *  true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
     * 
     * <li>对于任何非空引用值 x,x.equals(null) 都应返回 false。
     * </ul>
     * 
     * <p>
     * Object 类的 equals 方法实现对象上差别可能性最大的相等关系;
     * 即,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,
     * 此方法才返回 true(x == y 具有值 true)。
     * 
     * <p>
     * 注意:当此方法被重写时,通常有必要重写 hashCode 方法,
     * 以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
     *
     * @param   要与之比较的引用对象。
     * @return  如果此对象与 obj 参数相同,则返回 true;否则返回 false。
     * @see     #hashCode()
     * @see     java.util.HashMap
     */
    public boolean equals(Object obj) {
        return (this == obj);
    }

4.

问:如果需要您去维护一个类的hash散列表,如何设计,如何解决hash冲突?

答:我们在设计类的hash散列表时,不能保证每个元素的hash值都是不一样的,这样就会造成hash冲突。解决hash冲突有如下4种方法:

  • 开发定址法:既然当前位置容不下冲突的元素了,那就再找一个空的位置存储 Hash 冲突的值(当前 index 冲突了,那么将冲突的元素放在 index+1)。

  • 再散列法:换一个 Hash 算法再计算一个 hash 值,如果不冲突了就存储值(例如第一个算法是名字的首字母的 Hash 值,如果冲突了,计算名字的第二个字母的 Hash 值,如果冲突解决了则将值放入数组中)。

  • 链地址法:每个数组中都存有一个单链表,发生 Hash 冲突时,只是将冲突的 value 当作新节点插入到链表(HashMap 解决冲突的办法)。

  • 公共溢出区法:将冲突的 value 都存到另外一个顺序表中,查找时如果当前表没有对应值,则去溢出区进行顺序查找。

总结

  1. 当你真要的需要重写equals方法,这两点一定要记住:
  • A.如果两个对象相等(equals() 返回 true),那么它们的 hashCode()一定要相同;

  • B.如果两个对象hashCode()相等,它们并不一定相等(equals() 不一定返回 true)。

  1. 如果重写的equals方法但不重写hashCode,都是耍流氓,会有意想不到的结果。

  2. 重写hashCode方法时,尽可能将所有用于相等比较的参数都参与hashCode的计算。

  3. 建立hash散列表的意义就是在于,提高查询效率,当数据量大时,尤为显著。

参考文章:
Java中hashCode的作用
如何正确实现 Java 中的 HashCode
Java 的 equals 与 hashcode 对比分析
程序员必须搞清的概念equals和=和hashcode的区别
Android 面试准备之「equals 和 == 」

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容