重写equals为什么要重写hashCode(配合源码分析)

文章讲述了hashCode在Java中的作用,特别是在哈希表如HashSet和HashMap中的应用。当重写equals方法时,为了保证哈希表的行为正确,必须同时重写hashCode。通过一个QueryBO类的例子,展示了未重写和重写hashCode时,HashSet如何处理相等对象的不同结果。文章还简要分析了HashSet的源码,强调了hashCode在决定对象存储位置和去重过程中的关键角色。
摘要由CSDN通过智能技术生成

目录

一、hashCode的概念

二、为什么要有hashCode

三、为什么重写equals要重写hashCode

四、HashSet源码分析

五、容易记混的点


一、hashCode的概念

        hashCode()是Object定义的方法,它将返回一个整型值,这个方法通常用来将对象的内存地址转换为整数之后返回,它存在的价值是为Hash容器处理数据时提供支持,Hash容器可以根据hashCode定位需要使用的对象,也可以根据hashCode来排除2个不相同的对象,即:hashCode不同,则视为2个对象不同。

二、为什么要有hashCode

        哈希码主要在哈希表这类集合映射的时候用到,哈希表存储的是键值对(key-value),它的特点是:能根据“键”快速的映射到对应的“值”。这其中就利⽤到了哈希码。
例如 HashMap 怎么把 key 映射到对应的 value 上呢?用的就是哈希取余法,也就是拿哈希码和存储元素的数组的长度取余,获取 key 对应的 value 所在的下标位置。

三、为什么重写equals要重写hashCode

这里我们举例说明:

1、新建一个实体类QueryBO。

import lombok.Data;

/**
 * 查询
 *
 * @author yunyan
 * @date 2023/7/8
 */
@Data
public class QueryBO {

    private Boolean query1;
    private Boolean query2;
    private Boolean query3;
}

测试类:

/**
 * 测试hashCode
 *
 * @author yunyan
 * @date 2023/7/18
 */
public class TestHash {
    public static void main(String[] args) {
        QueryBO queryBO1=new QueryBO();
        queryBO1.setQuery1(true);
        queryBO1.setQuery2(true);
        queryBO1.setQuery3(false);

        QueryBO queryBO2=new QueryBO();
        queryBO2.setQuery1(false);
        queryBO2.setQuery2(false);
        queryBO2.setQuery3(false);

        System.out.println(queryBO1.equals(queryBO2));
    }
}

如果是原有的equals和hashCode方法,可以看出,如果没有重写equals方法,那么两个对象进行equals比较时,只有当两个对象的所有属性都相等时,才会认为两个对象相等。

结果:

 2、在QueryBO类里只重写equals方法

import lombok.Data;

import java.util.Objects;

/**
 * 查询
 *
 * @author yunyan
 * @date 2023/7/8
 */
@Data
public class QueryBO {

    private Boolean query1;
    private Boolean query2;
    private Boolean query3;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        QueryBO queryBO = (QueryBO) o;
        return Objects.equals(query3, queryBO.query3);
    }
}

结果:现在只要两个对象的query3属性相同,那么就认为两个对象相同

 3、继续测试,现在我使用HashSet集合将两个对象添加进去

import java.util.HashSet;
import java.util.Set;

/**
 * 测试hashCode
 *
 * @author yunyan
 * @date 2023/7/18
 */
public class TestHash {
    public static void main(String[] args) {
        QueryBO queryBO1=new QueryBO();
        queryBO1.setQuery1(true);
        queryBO1.setQuery2(true);
        queryBO1.setQuery3(false);

        QueryBO queryBO2=new QueryBO();
        queryBO2.setQuery1(false);
        queryBO2.setQuery2(false);
        queryBO2.setQuery3(false);

        System.out.println(queryBO1.equals(queryBO2));

        Set<QueryBO> set=new HashSet<>();
        set.add(queryBO1);
        set.add(queryBO2);
        System.out.println(set);
    }
}

结果是:

 这里我们都知道set集合是去重的,明明我们的两个对象都相等,但是set集合却认为是两个不同的对象。

原因是: HashSet、HashMap集合在添加元素的时候,用哈希取余法,也就是拿hashCode和存储元素的数组的长度取余,获取 key 对应的 value 所在的下标位置。如果获取到的下标位置上已经存在元素,则认为产生了哈希碰撞(hashCode() 所使用的散列算法也许刚好会让多个对象传回相同的散列值。)再拿新添元素(新的)与当前位置上的元素(旧的)进行equals比较,如果返回true就用新的替换旧的。如果返回false就添加到该位置下面的链表尾或红黑树上。而如果没有发生哈希碰撞,也就间接说明两个对象不相等。然而我们上面只是重写了equals方法(只比较query3是否相同),而hashCode返回的还是三个query属性组合成的数组的hashCode,所以虽然equals返回true,但是两个对象的hashCode不一样,set里存在两个QueryBO对象而不是一个。

现在在实体类里重写hashCode方法

@Override
    public int hashCode() {
        return Objects.hash(query3);
    }

看一下程序的运行结果:

 这就是为什么重写equals方法必须要重写hashCode方法的原因。

四、HashSet源码分析

下面我们查看HashSet的源码来分析情况

1、首先我们调用了HashSet的构造方法

 他的内容是创建一个新的HashMap

2、 原来HashSet是借助HashMap实现的,我们来看看他的add方法,通过idea中的Structure可以看到类的结构。

 3、到这里我们可以发现,这个add方法,调用的也是map的put方法。以传入的元素作为Key,PRESENT作为Value,而PRESENT其实是一个空对象。

 4、下面来看看put方法

 从这里可以看出来,HashMap对于节点的判断其实是利用了hash函数,而这个hash函数是借助了传入的Key的Hash函数

所以说,如果我们只重写了equals没有重写hashcode,就会发生与期望结果不相符的情况。

五、容易记混的点

  1. hashCode相等,两个字符串不一定相等。
  2. hashCode相等,这两个对象不一定相等。
  3. hashCode不等,这两个对象一定不相等。
  4. 如果两个对象分别调⽤ equals 方法都返回 true,则这两个对象一定相等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值