Java基础之浅谈hashCode()和equals()

 更新时间:2021年06月20日 17:18:54   作者:java技术爱好者_R  
今天给大家带来的是关于Java基础的相关知识,文章围绕着hashCode()和equals()展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下

写在前面

其实很早我就注意到阿里巴巴Java开发规范有一句话:只要重写 equals,就必须重写 hashCode

我想很多人都会问为什么,所谓知其然知其所以然,对待知识不单止知道结论还得知道原因。

hashCode方法

hashCode()方法的作用是获取哈希码,返回的是一个int整数

学过数据结构的都知道,哈希码的作用是确定对象在哈希表的索引下标。比如HashSet和HashMap就是使用了hashCode方法确定索引下标。如果两个对象返回的hashCode相同,就被称为“哈希冲突”。

equals方法

equals()方法的作用很简单,就是判断两个对象是否相等,equals()方法是定义在Object类中,而所有的类的父类都是Object,所以如果不重写equals方法则会调用Object类的equals方法。

Object类的equals方法是用“”号进行比较,在很多时候,因为号比较的是两个对象的内存地址而不是实际的值,所以不是很符合业务要求。所以很多时候我们需要重写equals方法,去比较对象中每一个成员变量的值是否相等。

问题来了

重写equals()方法就可以比较两个对象是否相等,为什么还要重写hashcode()方法呢?

因为HashSet、HashMap底层在添加元素时,会先判断对象的hashCode是否相等,如果hashCode相等才会用equals()方法比较是否相等。换句话说,HashSet和HashMap在判断两个元素是否相等时,会先判断hashCode,如果两个对象的hashCode不同则必定不相等

下面我们做一个试验,有一个User类,只重写equals()方法,然后放到Set集合中去重。

public class User {

    private String id;

    private String name;

    private Integer age;
    
    public User(String id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(id, user.id) &&
            Objects.equals(name, user.name) &&
            Objects.equals(age, user.age);
    }
    
    //getter、setter、toString方法
}

然后我们循环创建10个成员变量的值都是一样的User对象,最后放到Set集合中去重。

public static void main(String[] args) {
    List<User> list = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        User user = new User("1", "张三", 18);
        list.add(user);
    }
    Set<User> set = new HashSet<>(list);
    for (User user : set) {
        System.out.println(user);
    }
    List<User> users = list.stream().distinct().collect(Collectors.toList());
    System.out.println(users);
}

按道理我们预期会去重,只剩下一个“张三”的user,但实际上因为没有重写hashCode方法,所以没有去重。

接着我们在User类里面重写一些hashCode方法再试试,其他不变。

public class User {
    //其他不变
    
    //重写hashCode方法
    @Override
    public int hashCode() {
        return Objects.hash(id, name, age);
    }
}

再运行,结果正确。

究其原因在于HashSet会先判断hashCode是否相等,如果hashCode不相等就直接认为两个对象不相等,不会再用equals()比较了。我们不妨看看重写hashCode方法和不重写hashCode方法的哈希码。

这是不重写hashCode方法的情况,每个user对象的哈希码都不一样,所以HashSet会认为都不相等。

这是重写hashCode方法的情况,因为是用对象所有的成员变量的值计算出的哈希码,所以只要两个对象的成员变量都是相等的,则生成的哈希码是相同的。

那么有些人看到这里,就会问,如果两个对象返回的哈希码都是一样的话,是不是就一定相等

答案是不一定的,因为HashSet、HashMap判断哈希码相等后还会再用equals()方法判断。

总而言之:

  • 哈希码不相等,则两个对象一定不相同。
  • 哈希码相等,两个对象不一定相同。
  • 两个对象相同,则哈希码和值都一定相等。

总结

所以回到开头讲的那句,只要重写 equals,就必须重写 hashCode,这是一个很重要的细节,如果不注意的话,很容易发生业务上的错误。

特别是有时候我们明明用了HashSet,distinct()去重,但是就是不生效,这时应该回头看看重写了equals()和hashCode()方法了吗?

到此这篇关于Java基础之浅谈hashCode()和equals()的文章就介绍到这了,更多相关hashCode()和equals()内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java 批量获取地址间距离工具(支持中转站)

    Java 批量获取地址间距离工具(支持中转站)

    本文主要介绍了Java批量获取地址间距离,获取两个地址间距离,实现方式比较多,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • Java编程中的equals方法使用全解

    Java编程中的equals方法使用全解

    这篇文章主要介绍了Java编程中的equals方法使用全解,是Java入门学习中的基础知识,需要的朋友可以参考下
    2015-10-10
  • java获取优酷视频地址示例

    java获取优酷视频地址示例

    使用JAVA解析优酷视频页,得到视频真实地址, 还可以下载,需要的朋友可以参考下
    2014-03-03
  • Spring Gateway自定义请求参数封装的实现示例

    Spring Gateway自定义请求参数封装的实现示例

    这篇文章主要介绍了Spring Gateway自定义请求参数封装的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • Java经典排序算法之归并排序实现代码

    Java经典排序算法之归并排序实现代码

    这篇文章主要介绍了Java经典排序算法之归并排序实现代码,归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用,将已有序的子序列合并,得到完全有序的序列,需要的朋友可以参考下
    2023-10-10
  • springboot使用redis对单个对象进行自动缓存更新删除的实现

    springboot使用redis对单个对象进行自动缓存更新删除的实现

    本文主要介绍了springboot使用redis对单个对象进行自动缓存更新删除的实现,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • BaseDao封装增删改查的代码详解

    BaseDao封装增删改查的代码详解

    本篇文章主要介绍对数据库中表中的数据进行增改删查询,封装一个工具类(BaseDao)的详细使用以及部分理论知识,并通过代码示例给大家介绍的非常详细,需要的朋友可以参考下
    2024-03-03
  • springboot上传文件并返回url过程

    springboot上传文件并返回url过程

    这篇文章主要介绍了springboot上传文件并返回url过程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • Spring Boot线程池使用的一些实用心得

    Spring Boot线程池使用的一些实用心得

    理论上线程越多程序可能更快,但在实际使用中我们需要考虑到线程本身的创建以及销毁的资源消耗,以及保护操作系统本身的目的我们通常需要将线程限制在一定的范围之类,这篇文章主要给大家介绍了关于Spring Boot线程池使用的一些实用心得,需要的朋友可以参考下
    2021-09-09
  • Spring IOC中的Bean对象用法

    Spring IOC中的Bean对象用法

    这篇文章主要介绍了Spring IOC中的Bean对象用法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09

最新评论