一文搞懂为什么要同时重写equals方法和hashCode方法+实例分析

一、我们先认识一下Java自带的equals方法和hashCode方法,两者都在Object类中,源码如下:

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

hashCode方法由native修饰,说明他是一个本地方法,它是由C或C++编写的一个方法
equals方法底层可以看出是通过==来实现的,所以它其实比较的是两个对象的引用是否相等,即两者指向的内存地址是否相同。

二、为什么要同时重写equals方法和hashCode方法
首先说一下重写equals方法的目的:比如有两个new出来的Student对象A和B,两个对象的全部字段属性值完全相同(名字、年龄、唯一身份标识等等),这时虽然两者的内存地址不同,但两者的内容完全相同,我们认为他俩就是完全一样的,就是同一个对象。如果不重写equals方法,显然因为A和B的内存地址不同,比较结果自然为false,就无法达到我们的预期结果true,所以我们要重写equals方法使得当两个对象的内容完全一致时,我们就认定他们两个的引用相等,即是两个相同的对象,不再去管它俩的内存地址是否相同。
然后回归正文——
为什么要同时重写equals方法和hashCode方法呢?分析如下:
在重写equals方法的前提下,上面两个对象A和B由于内存地址不同,那么当不重写hashCode方法时,两者的hash值必定是不同的。但是当我们使用集合时,如HashSet、HashMap是不允许存在重复值的。如果不重写hashCode方法,比如根据HashMap集合的存取机制(先根据hash值判断所处位置,再利用equals方法比较存取的对象是否相等),A和B显然都可以存入集合中,然而A和B的内容是相同的,这违背了集合内容不能重复的原则,所以我们必须重写hashCode方法使同属一个类的两个具有相同值的对象(如本文中的A和B)的Hash值相等,这样就能确保集合中不会存取相同的内容了。
三、实例分析
下面做个实验,用同一个Student类创建两个实例对象,然后比较各种情况下,这两个对象是否相等。
1、先将Student中的hashCode()方法注释掉

package com;
 
import java.util.Objects;
 
public class Student {
    private int age;
    private String name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Student() {
        super();
    }
    public Student(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }
 
  /*  @Override
    public int hashCode() {
        return Objects.hash(age, name);
    }*/
}

进行如下测试:

package com.school.eution.accommodation;
 
import java.util.HashMap;
import java.util.Map;
 
public class Test {
    public static void main(String[] args) {
        Student s1 = new Student(1,"哈哈哈");
        Student s2 = new Student(1,"嘻嘻嘻");
 
        Map<Object,String> testMap = new HashMap<Object,String>();
        testMap.put(s1, "美国学生");
        System.out.println(s1.hashCode());
        System.out.println(testMap.get(s1));
 
        System.out.println("------------------");
        System.out.println(s2.hashCode());
        System.out.println(testMap.get(s2));
 
        System.out.println("------------------");
        System.out.println(s1.equals(s2));
    }
} 

多次运行后结果一致:

971848845
美国学生
------------------
1910163204
null
------------------
true

解释:如果不重写hashCode方法就会使用从Object继承来的本地hashCode()方法,所以将对象s1放入Map中后,通过s1对象能从Map中取出来字符串“美国学生”,但是s2和s1在值上是相等的,却不能使用s2对象从Map中也获取到同样的值。这种情况就是因为没有重写hashCode方法,导致s1和s2使用默认hasCode方法产生的Hash值不一致造成的。所以s2取值的时候,按照s2的hash值“1910163204”自然在Map中就找不到了。
但是如果不使用Map取值,直接通过equals比较s1和s2则是相等的(此时并没有重写hashCode)。

Map取值是先通过hash值定位,如果hash值存在于Map中,并且hash值一致就说明在Map的同一个链表中,继续使用equals方法比较值是否相等,如果hash值都不一致,就没必要往下比较了直接返回false。

下面将Student中重新hash的代码放开

package com;
 
import java.util.Objects;
 
public class Student {
    private int age;
    private String name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Student() {
        super();
    }
    public Student(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(age, name);
    }
}

再次运行结果:

2321474
美国学生
------------------
2321474
美国学生
------------------
true

不难发现,即使s2事先并没有向Map中存放,由于我们重写了hashCode方法,使得s1和s2的哈希值相同,且此时s1与s2的字段属性值又是相等的,所以s2的值我们就可以借助s1存放到Map中的值而得到。就好比s1与s2拿了同一把钥匙(key)。通过equals方法比较s1与s2也依然是相等的。

总结:为什么一定要同时重写hashCode()方法和equals()方法:
这其实是针对HashSet和HashMap等这类使用hash值存储的对象而言的。比如:在向HashSet存放Student时,如果没有重写hashCode,这时往HashSet中存放s1、s2实际上是都可以存进去的,即使s1与s2的值完全一致,这显然违背了该集合中不能存放相同内容的原则。
另外如果用不到这些Hash的集合的话,只重写equals()方法也能满足两个对象的内容是否相等的比较。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值