重写equals必须重写hashcode吗?(原理分析)

重写equals 和 重写hashcode的必要性和原理分析

首先上Object类的源码:

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

equals方法和hashcode方法是Object类的两个方法。
(1)equals():原始的Object类的equals方法比较两个对象的地址,如果地址相等,则返回true,否则返回false。因为JAVA所有的类继承于Object类,所以,java中的类均有equals方法,equals()默认比较对象地址。所以一般的对象用equals方法比较时,对应的类都会重写equals方法,例如比较对象的某个实例域
(String类就重写了equals方法,比较的就是字符串内容,而不是String类对象的地址)
(2)hashCode():Object类的hashCode方法是一个本地方法(没有方法体,具体方法实现由非java代码实现(更贴近操作系统的语言),hashCode()默认返回的是对象的地址的hash值。)

所以默认的equals方法和hashCode方法是有某种对应关系的
默认情况下,equals方法返回对象地址,hashCode返回对象地址的hashCode值,即:同一对象,equals返回true,hashCode返回值相同。
实例:

Student s1=new Student("jonsnow");
Student s2=s1;
System.out.println(s1.equals(s2));
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());

运行结果:
在这里插入图片描述
但是在JAVA中,两个不同的对象的地址必然不同,所以如果不重写equals方法,那么普通的自定义类的实例的equals必然会返回false。但是一般情况下我们比较两个对象,并不是比较两个对象的地址(因为这意义不大),而是比较对象的某个实例域的值。例如Student类的stuName:

    @Override
    public boolean equals(Object s)
    {
       if(s instanceof Student)
       {
           Student s1=(Student)s;
           if(this.stuName.equals(s1.stuName))return true;
           else return false;
       }
       else
       {
           return false;
       }
    }

这个例子就是重写了equals方法,比较了Student类的stuName实例域。
那么现在就有一个问题了,当我只重写了equals,而没有重写hashCode,那么equals和hashCode的一致性便被破坏:

		Student s1=new Student("jonsnow");
        Student s2=new Student("jonsnow");
        System.out.println(s1.equals(s2));
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());

在这里插入图片描述
由结果可知,两个对象的equals判断相等,而他们的哈希值却不相等

这里需要讨论的是,当这两者不一致会怎么样?

我认为,保持equals和hashCode一致是更符合逻辑的,并且是一个良好的开发习惯。但是,如果我只需要使用equals而不需要hashCode,那么重写equals不一定需要重写hashCode。然而,在某些特定情境下,尤其是集合框架(HashMap,HashSet等)的使用环境下,重写equals必须重写hashCode,否则会发生因为不一致而产生的混乱。

首先,我们需要了解HashMap的存储原理(扩容等不详细分析):

HashMap是一个数组加链表的结构,即以数组实现散列结构,每个数组元素是一个链表,存储相同hash值的基本元素。
在这里插入图片描述
每个黑色的圆代表存储在HashMap里的每个基本元素,例如:

  		HashMap<Student,Integer> hashMap=new HashMap();
        Student a=new Student("abc");
        hashMap.put(a,1);

将Student类的实例a存储在HashMap中,HashMap首先会计算a的hashCode,再经过一次hash,得到存储在数组中的位置。当该位置已有对象s存在时,会调用equals方法比较两个对象是否相等,如果相等,就更新原对象s0的值为s1的值,如果不相等,就在链表后添加新对象s1。
实例:
(Student类未重写equals方法和hashCode方法)

  		HashMap<Student,Integer> hashMap=new HashMap();
        Student s=new Student("abc");
        Student a=new Student("abc");
        hashMap.put(s,1);
        hashMap.put(a,2);
        System.out.println("两个对象的equals返回值为"+s.equals(a));
        System.out.println("两个对象的hashCode值分别为 s:"+s.hashCode()+",a:"+a.hashCode());
        System.out.println("hashMap的大小为:"+hashMap.size());
        System.out.println("对象s的值为:"+hashMap.get(s));
        System.out.println("对象a的值为:"+hashMap.get(a));

在这里插入图片描述
结果符合预期的结果,两个对象的地址不同,所以他们的equals方法返回为false,hashCode值不同,两个对象都可以放在HashMap中。

现在,重写Student类的equals方法:

@Override
    public boolean equals(Object s)
    {
       if(s instanceof Student)
       {
           Student s1=(Student)s;
           if(this.stuName.equals(s1.stuName))return true;
           else return false;
       }
       else
       {
           return false;
       }
    }

equals比较两个对象的stuName域的值。
那么,如果代码不变,当a对象放入HashMap中时,a等于s(a.equals(s)==true),应当更新s的值为2,并且HashMap的size为1。然而实际结果为:
在这里插入图片描述
HashMap将a对象作为一个新的基本元素添加进去了。

因为a对象和s对象的Hash值(地址不同,所以hash值不同)不同,HashMap将a元素当做了新的元素。

那么,现在同时重写hashCode方法,返回字符串的hash值(当字符串相等时,hash值也相等)。

@Override
    public int hashCode()
    {
        return this.stuName.hashCode();
    }
    @Override
    public boolean equals(Object s)
    {
       if(s instanceof Student)
       {
           Student s1=(Student)s;
           if(this.stuName.equals(s1.stuName))return true;
           else return false;
       }
       else
       {
           return false;
       }
    }

运行结果:
在这里插入图片描述
那么现在,结果符合预期。a对象与s对象相等,那么将a对象添加到HashMap中,会更新s对象的值。

所以,一般情况下,重写equals尽量重写hashCode,在使用HashMap等集合框架时,重写equals必须重写hashCode。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值