为什么重写equals()时一定要重写hashCode()?

1. 为什么重写equals()时一定要重写hashCode()?

面试回答:

  1. java官方文档规定重写equals()时一定要重写hashCode()。 equals()和 hashCode()是 Object 类中的两个基础方法,Object 中的 equals( )和hashCode( ),使用==判断,即比较两个对象的地址值。

  2. 但是在实际的开发中我们需要比较两个字符串对象的内容是否相等,所以需要重写euqals()。重写后的equals()先比较对象的地址值,再比较字符串对象的内容(通过循环遍历每个字符)

  3. 哈希表比较两个对象是否相等,是先比较两个对象的hashcode值,所以需要重写hashcode(),(hashcode()不重写比较的是对象的引用地址,不是哈希值 ),使用hashcode()比较结果是fasle 则equals()不会再执行,若hashcode()返回true,再用equals()比较,之所以这样设计就是为了提高效率。

以上可以总结为:

两个对象使用equals比较相同,hashCode一定相同

hashCode相同,equals()比较不一定相同,可能出现hash冲突

hashCode不同,equals()一定不同

拓展:

底层基于哈希表的数据结构和类主要有以下几种:

  • HashMap :最常用的键值对存储容器,底层是数组加链表(或红黑树)实现。通过 hashCode() 将键进行哈希计算,从而快速定位键值对的存储位置。

  • HashSet :基于 HashMap 实现的无序集合,元素不能重复。它使用 HashMap 的键来存储元素,值是固定的虚拟对象。

  • LinkedHashMap :继承自 HashMap,保留插入顺序。它在 HashMap 的基础上增加了一个双向链表来记录元素的插入顺序。

  • LinkedHashSet :继承自 HashSet,同时保留元素的插入顺序。底层基于 LinkedHashMap 实现。

  • Hashtable :类似 HashMap 的线程安全版本,但效率较低,已经被逐步淘汰。它也是基于哈希表实现的键值对存储。

  • ConcurrentHashMap:线程安全的哈希表实现,支持高并发环境下的键值对存储。通过分段锁机制提高并发性能。

2.举例说明

2.1例一

java官方文档规定重写equals()时一定要重写hashCode(),equals()默认是使用的Object类下的方法,使用==判断,即比较两个对象的地址值,但是在实际的开发中我们需要比较两个字符串对象的内容是否相等,所以需要重写euqals()。

举例:

如果不重写equals(),字符串内容相等,会出现误判

如:String a = "123456",String b = "123456",在 equals 方法看来,两个字符串的内存地址不相等,所以仍然会判定为不相等,这就与我们期望的判断结果发生了矛盾,所以需要去重写 equals 方法,只有重写了equals 方法之后,我们再去判断 a 和 b,此时判断结果就会相等。

2.2 例二

使用哈希表数据结构的例子:

如HashSet

Set 集合是用来保存不同对象的,相同的对象就会被 Set 合并,最终留下一份独一无二的数据。

重写equals和hashCode的情况下:

String类底层重写过了equals()和hashCode()

java 复制代码
import java.util.HashSet;
import java.util.Set;
 
/**
 *
 */
public class EqualsHashCodeTest {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        set.add("Java");
        set.add("Java");
        set.add("SpringBoot");
        set.add("SpringBoot");
        set.add("SpringBoot");
        set.add("Redis");
        System.out.println("set集合的长度为:" + set.size());
        set.forEach(System.out::println);
    }
}

上述结果可以看出,重复的数据已经被 Set 集合"合并"了,这也是 Set 集合最大的特点:去重。

上面的案例种,set集合的泛型是String,这个类已经重写过了equals和hashCode,

那么下面我们将泛型修改为一个自定义的类,而在这个自定义的类中,只重写equals方法。

只重写equals( ),不重写hashcode( ):

java 复制代码
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
 
/**
 *
 */
class Student {
    private String name;
    private Integer age;
 
    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Integer getAge() {
        return age;
    }
 
    public void setAge(Integer age) {
        this.age = age;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(name, student.name) && Objects.equals(age, student.age);
    }
 
//    @Override
//    public int hashCode() {
//        return Objects.hash(name, age);
//    }
 
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
 
public class EqualsHashCodeTest {
    public static void main(String[] args) {
        Set<Student> set = new HashSet<>();
        set.add(new Student("张起灵", 18));
        set.add(new Student("张起灵", 18));
        System.out.println("set集合的长度为:" + set.size());
        set.forEach(System.out::println);
    }
}

从上述代码和上述图片可以看出,即使两个对象是相等的,Set 集合竟然没有将二者进行去重与合并。这就是重写了 equals 方法,但没有重写 hashCode 方法的问题所在。

解决上述问题,自然就是在重写equals的同时一定要重写hashCode!!

修改后

java 复制代码
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
 
/**
 *
 */
class Student {
    private String name;
    private Integer age;
 
    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Integer getAge() {
        return age;
    }
 
    public void setAge(Integer age) {
        this.age = age;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(name, student.name) && Objects.equals(age, student.age);
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
 
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
 
public class EqualsHashCodeTest {
    public static void main(String[] args) {
        Set<Student> set = new HashSet<>();
        set.add(new Student("张起灵", 18));
        set.add(new Student("张起灵", 18));
        System.out.println("set集合的长度为:" + set.size());
        set.forEach(System.out::println);
    }
}

过程详细分析:

通过上述结果可以看出,当我们一起重写了两个方法之后,奇迹的事情又发生了,Set 集合又恢复正常了,这是为什么呢?

出现以上问题的原因是,如果只重写了 equals 方法,那么默认情况下,Set 进行去重操作时,会先判断两个对象的 hashCode 是否相同,此时因为没有重写 hashCode 方法,所以会直接执行 Object 中的 hashCode 方法,而 Object 中的 hashCode 方法对比的是两个不同引用地址的对象(new了两次Student,那这两个对象自然就指向了不同的引用咯),所以结果是 false,那么 equals 方法就不用执行了,直接返回的结果就是 false:两个对象不是相等的,于是就在 Set 集合中插入了两个相同的对象。

但是,如果在重写 equals 方法时,也重写了 hashCode 方法 ,那么在执行判断时会去执行重写的 hashCode 方法,此时对比的是两个对象的所有属性的 hashCode 是否相同,于是调用 hashCode 返回的结果就是 true,再去调用 equals 方法,发现两个对象确实是相等的,于是就返回 true 了,因此 Set 集合就不会存储两个一模一样的数据了,于是整个程序的执行就正常了。

如果以上不明白,请你仔细阅读下面文章

文章参考:https://blog.csdn.net/weixin_43823808/article/details/124035897

相关推荐
程序猿零零漆几秒前
SpringCloud 系列教程:微服务的未来(二)Mybatis-Plus的条件构造器、自定义SQL、Service接口基本用法
java·spring cloud·mybatis-plus
猿来入此小猿2 分钟前
基于SpringBoot在线音乐系统平台功能实现十二
java·spring boot·后端·毕业设计·音乐系统·音乐平台·毕业源码
易码智能4 分钟前
【EtherCATBasics】- KRTS C++示例精讲(2)
开发语言·c++·kithara·windows 实时套件·krts
一只自律的鸡5 分钟前
C语言项目 天天酷跑(上篇)
c语言·开发语言
程序猿000001号7 分钟前
使用Python的Seaborn库进行数据可视化
开发语言·python·信息可视化
一个不正经的林Sir12 分钟前
C#WPF基础介绍/第一个WPF程序
开发语言·c#·wpf
愤怒的代码16 分钟前
Spring Boot对访问密钥加解密——HMAC-SHA256
java·spring boot·后端
带多刺的玫瑰17 分钟前
Leecode刷题C语言之切蛋糕的最小总开销①
java·数据结构·算法
API快乐传递者17 分钟前
Python爬虫获取淘宝详情接口详细解析
开发语言·爬虫·python
公众号Codewar原创作者19 分钟前
R数据分析:工具变量回归的做法和解释,实例解析
开发语言·人工智能·python