你真的了解equal()和hashCode()?

本文从下面几个方面来分析:

1.equals() 的作用是什么?

2.equals() 与 == 的区别是什么?

3.hashCode() 的作用是什么?

4.hashCode() 和 equals() 之间有什么联系?

第1部分 equals() 的作用

equals() 的作用是 用来判断两个对象是否相等。

- equals() 定义在JDK的Object.java中,通过判断两个对象的地址是否相等(即,是否是同一个对象)来区分它们是否相等。在Object.java中的源码如下:


public boolean equals(Object obj) {

    return (this == obj);

}

既然这个方法在Object中,这就意味着所有的对象都有这个方法,从源码中可以看出,默认的equals()与“==”是等价的。因此,我们通常会覆写equals()方法:若两个对象的内容相等,则equals()方法返回true;否则,返回fasle。 就是说:

1) 若某个类没有覆盖equals()方法,当它的通过equals()比较两个对象时,实际上是比较两个对象是不是同一个对象。这时,等价于通过“==”去比较这两个对象。

2) 我们可以覆盖类的equals()方法,来让equals()通过其它方式比较两个对象是否相等。通常的做法是:若两个对象的内容相等,则equals()方法返回true;否则,返回fasle。

第2部分 equals() 与 == 的区别是什么?

== : 它的作用是判断两个对象的内存地址(或说引用)地址是不是相等。即,判断两个对象是不是同一个对象(对于基本数据类型,如int,比较的值;对于引用类型,如Integer,比较的是引用地址,就是说它们是否指向同一个对象)。

equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况(前面第1部分已详细介绍过):

      情况1,类没有覆写equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。

      情况2,类覆盖了equals()方法。一般,我们通过覆写equals()方法来判断两个对象的内容是否相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。典型的覆盖内容的equals()写法:

private static class Person {

        int age;

        String name;

        public String toString() {

            return "(" + name + ", " + age + ")";

        }

        /**

        * @desc 覆盖equals方法

        */

        @Override

        public boolean equals(Object obj) {

            if (obj == null) {

                return false;

            }

            //如果是同一个对象返回true,反之返回false

            if (this == obj) {

                return true;

            }

            //判断是否类型相同

            if (this.getClass() != obj.getClass()) {

                return false;

            }

            Person person = (Person) obj;

            return name.equals(person.name) && age == person.age;

        }

    }

 第3部分 hashCode() 的作用

      hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。

    hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。

    虽然,每个Java类都包含hashCode() 函数。但是,仅仅当创建并某个“类的散列表”时,该类的hashCode() 才有用(作用是:确定该类的每一个对象在散列表中的位置;其它情况下(例如,创建类的单个对象,或者创建类的对象数组等等),类的hashCode() 没有作用。

      上面的散列表,指的是Java集合框架中有关散列表的类,如HashMap,Hashtable,HashSet。

      也就是说:hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。从HashMap.put()源码很容易看出来:

public V put(K key, V value) {

    return putVal(hash(key), key, value, false, true);

}

static final int hash(Object key) {

    int h;

    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>>

16);

}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,

              boolean evict) {

    Node<K,V>[] tab; Node<K,V> p; int n, i;

    if ((tab =

table) ==

null || (n = tab.length) ==

0)

        n = (tab = resize()).length;

    if ((p = tab[i = (n - 1) & hash]) == null)

        tab[i] = newNode(hash, key, value, null);

    else {

    //....限于篇幅这里省略部分源码,

//但不影响问题的分析

}

    ++modCount;

    if (++size >

threshold)

        resize();

    afterNodeInsertion(evict);

    return null;

}

第4部分 hashCode() 和 equals() 的关系

按照“类的用途”分两种情况

情况一、类不使用在散列表的数据结构中,如:HashMap、HashSet等

这种情况,该类的hashCode()与equals()没有任何半毛关系,就是说equals() 用来比较该类的两个对象是否相等。而hashCode() 则根本没有任何作用,所以,不用理会hashCode()

情况二、类的对象用在了类似HashMap、HashSet这样的散列表数据结构中

  在这种情况下,该类的“hashCode() 和 equals() ”是有关系的:

        1)、如果两个对象相等,那么它们的hashCode()值一定相同。

              这里的相等是指,通过equals()比较两个对象时返回true(再次强调说明:equals()返回true,意味着要么指向的是同一个对象,要么内容一致)。

        2)、如果两个对象hashCode()相等,它们并不一定相等。

        因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等。补充说一句:“两个不同的键值对,哈希值可能相等”,这就是哈希冲突

恶补:为什么会有产生冲突?简单的说,hash法是一种映射算法,一种压缩算法,很简单的一个场景,试想一下,你们班上有30个同学,其中只有5个女生,这不是僧多粥少吗?一个萝卜能有一个坑吗?能不冲突吗?!)。

        此外,在这种情况下。若要判断两个对象是否相等,除了要覆盖equals()之外,也要覆盖hashCode()函数。否则,equals()无效。

例如,创建Person类的HashSet集合,必须同时覆盖Person类的equals() 和 hashCode()方法。

        如果单单只是覆盖equals()方法。我们会发现,equals()方法没有达到我们想要的效果。

总之:{obj1.equals(obj2)}==》{(obj1.hashCode())  == obj2.hashCode()},对象相等与对象的hashCode相等是充分非必要条件

效果分析:

import java.util.HashSet;

/**

*

* @author java栈长

* @desc 比较equals() 返回true 或 false时, hashCode()的值。

* <p>

* debug时,注释Person类的hashCode(),查看输出结果:

* <p>

* 一、不覆写hashCode(): //debug时注释hashCode()即可

* 输出

* p1.equals(p2) : true; p1(1174361318) p2(589873731)

* set:[(eee, 100), (eee, 100), (aaa, 200)]  //hashSet中出现重复元素,因为p1和p2的hashCode()不相同

* <p>

* 二、覆写Person类的hashCode() ------>正确姿势

* 输出:

* p1.equals(p2) : true; p1(37) p2(37)

* set:[(a, 100), (b, 200)]  //hashSet中没有重复元素,p1和p2的hashCode相同了,equals()也生效了。

*/

public class HashCodeTest {

    public static void main(String[] args) {

        // 新建Person对象,

        Person p1 =

new Person("a", 100);

        Person p2 =

new Person("a", 100);

        Person p3 =

new Person("b", 200);

        // 新建HashSet对象

        HashSet set =

new HashSet();

        set.add(p1);

        set.add(p2);

        set.add(p3);

        // 比较p1 和 p2, 并打印它们的hashCode()

        System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());

        // 打印set

        System.out.printf("set:%s\n", set);

    }

    /**

    * @desc Person类。

    */

    private static class Person {

        int age;

        String

name;

        @Override

        public int hashCode() {

            int nameHash =

name.toUpperCase().hashCode();

            return nameHash ^

age;

        }

        public Person(String name, int age) {

            this.name = name;

            this.age = age;

        }

        public String

toString() {

            return "(" +

name +

", " +

age +

")";

        }

        /**

        * @desc 覆盖equals方法

        */

        @Override

        public boolean equals(Object obj) {

            if (obj ==

null) {

                return false;

            }

            //如果是同一个对象返回true,反之返回false

            if (this == obj) {

                return true;

            }

            //判断是否类型相同

            if (this.getClass() != obj.getClass()) {

                return false;

            }

            Person person = (Person) obj;

            return name.equals(person.name) &&

age == person.age;

        }

    }

}

输出结果分析:

一、不覆写hashCode(): //debug时注释hashCode()即可

输出:

p1.equals(p2) : true; p1(1174361318) p2(589873731)

set:[(eee, 100), (eee, 100), (aaa, 200)]  //hashSet中出现重复元素,因为p1和p2的hashCode()不相同,hashSet中hashCode不的

思考:p1和p2的hashCode为什么不同,jvm是如何生成这个hashCode的?

请移步:《java类的hashCode》一文

二、覆写hashCode() ------>正确姿势

输出:

p1.equals(p2) : true; p1(37) p2(37)

set:[(a, 100), (b, 200)]  //hashSet中没有重复元素,p1和p2的hashCode相同了,equals()也生效了。

更多干货在公号【java栈长】!

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