关于 equals() 和 hashcode() 你需要知道的一切

1. equal() 和 == 的区别是什么?

要理解 equals() 方法,就必须从 == 开始讲起了。

== 的作用很简单,就是判断两个对象的地址是不是相等(基本数据类型比较的是值,引用数据类型比较的是地址)。

那么 equal() 方法呢?它是 Object 对象的一个方法,因为所有类都继承自 Object,所以所有类都有 equal() 方法。我们可以看看 Object 的源码中的 equal() 方法到底是长什么样的:

public boolean equals(Object obj) {
        return (this == obj);
    }

你会想,这不就是和 == 一样??是的,的确是一模一样,那么为什么还需要多弄出一个 equals() 方法呢?其实 equals() 方法就是 Java 提供给我们让我们重写的。一般,我们使用 equals() 方法来比较两个对象的内容是否相等,来看下面这个例子:

/**
 * @Author shenghai
 * @Date 4/19 19:03
 */
public class test {
    public static void main(String[] args) {
        Person p1 = new Person("abc", 111);
        Person p2 = new Person("abc", 111);
        System.out.printf("p1.equals(p2) : %s\n", p1.equals(p2));
        System.out.printf("p1==p2 : %s\n", p1==p2);
    }

    private static class Person {
        int age;
        String name;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        @Override
        public boolean equals(Object obj){
            if(obj == null) {
                return 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; //这里name后的equals是String类重写的
        }
    }
}

/**
 * 运行结果:
 * p1.equals(p2) : true
 * p1==p2 : false
 */

可以看到,在 Person 对象中,我们重写了 equals() 方法,主要的逻辑在最后一句,判断 Person 类的字段 nameage是否都相等。这样,我们就使用了新的规则来判断两个 Person 对象是否相等,而不是使用原始的 == 来判断两个 Person 对象的地址是否相等。

2. hashCode() 的作用

和 equals() 一样,hashCode() 也是 Object 类中的一个方法,它是一个 native 方法(即JNI方法,Java Native Interface,源码用C写的)。

public native int hashCode();

hashCode() 的作用是获取哈希码,它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的位置。因此,hashCode() 只有在哈希表中才有用,在其他情况下都是没用的。这里的哈希表包括 HashMap、HashSet、HashTable等。

那么为什么要有 hashCode 呢?

以稍微简单一些的 HashSet 为例,当你将对象加入 HashSet 时,HashSet 会先计算对象的hashCode值来获得对象加入的位置,同时也会与该位置其他已经加入的对象的hashCode值进行比较,这个时候就会调用 equals() 方法来检查 hashCode 相等的对象是否“真的相等”(即用我们自己重写的 equals() 方法来判断)。如果“真的相等”,HashSet 就不会让其加入操作成功。如果不相等,那么就发生了哈希碰撞,会将这个对象重哈希到其他位置。

3. hashCode() 与 equals() 的关系

这里需要分为两种情况来讨论。

3.1 没有在哈希表中使用

这里的意思是说,假设我们有一个对象Obj,但是我们没有在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到这个对象。

那么,这种情况下,无论你怎么重写 equals() 方法,都跟hashCode() 方法一点关系都没有!!

3.2 在哈希表中使用

但是,如果我们使用到了哈希表,我们就需要保证:如果两个对象相等,那么它们的 hashCode 值一定相同(这里的相等是指,通过equals()比较两个对象时返回true)。

因此,在这样的情况下,如果要判断两个对象是否相等,除了要重写 equals() 方法外,也要重写 hashCode() 方法。

假设我们只重写了 equals() 方法,没有重写 hashCode() 方法,那么即使在两个对象相等的时候( equals() 方法返回true),这两个对象还是没有办法哈希到同一个地址下,这时候使用哈希表就会出问题。

举个例子:

import java.util.HashSet;

/**
 * @Author shenghai
 * @Date 4/20 13:20
 */
public class test1 {
    public static void main(String[] args) {
        // 新建Person对象,
        Person p1 = new Person("abc", 111);
        Person p2 = new Person("abc", 111);
        Person p3 = new Person("ccc", 200);

        // 新建HashSet对象
        HashSet<Person> 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);
    }

    private static class Person {
        int age;
        String name;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        @Override
        public boolean equals(Object obj) {
            if(obj == null) {
                return 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;
        }
    }
}

/**
 * 运行结果:
 * p1.equals(p2) : true; p1(460141958) p2(1163157884)
 * set:[euqalsAndHashcode.test1$Person@74a14482, euqalsAndHashcode.test1$Person@4554617c,
 * euqalsAndHashcode.test1$Person@1b6d3586]
 */

这个时候,尽管我们使用 equals() 方法能够返回true。但是显然,在将 p1 和 p2 添加进 HashSet 的时候,HashSet 仍然判定这两个对象不相等。

为什么会出现这种情况呢?这是因为虽然 p1 和 p2 的内容相等,但是它们的hashCode()不等;所以,HashSet在添加p1和p2的时候,认为它们不相等。所以,我们现在重写 hashCode() 方法试试。

import java.util.HashSet;
import java.util.Objects;

/**
 * @Author shenghai
 * @Date 4/20 13:20
 */
public class test2 {
    public static void main(String[] args) {
        // 新建Person对象,
        Person p1 = new Person("abc", 111);
        Person p2 = new Person("abc", 111);
        Person p3 = new Person("ccc", 200);

        // 新建HashSet对象
        HashSet<Person> 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);
    }

    private static class Person {
        int age;
        String name;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        @Override
        public boolean equals(Object obj) {
            if(obj == null) {
                return 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;
        }

        @Override
        public int hashCode() {
            return Objects.hash(age, name);
        }
    }
}

/**
 * 运行结果:
 * p1.equals(p2) : true; p1(100756) p2(100756)
 * set:[euqalsAndHashcode.test2$Person@18994, euqalsAndHashcode.test2$Person@19bfc]
 */

重写了 equals() 方法和 hashCode() 方法之后,一切就都没有问题啦~

最后,总结一下 equals() 方法和 hashCode() 方法的相关规定:

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

推荐阅读更多精彩内容