为什么重写 equals 还要重写 hashCode 方法?

news/2024/7/5 3:49:49

关于equals与hashCode关系的描述

我们可以先来看一下这个定理

(1)如果两个对象的 hashCode 相等的情况下,对象的内容值不一定相等(hash碰撞问题)

(2)如果使用 equals 方法比较两个对象内容值相等的情况下,则两个对象的hashCode值一定要相等

相信有看过类似文章的小伙伴都可以回忆起一些什么来! 

我们可以用代码的形式来解释上述问题

public class Demo {
    public static void main(String[] args) {
        String a1 = "a";
        Integer a2 = 97;
        System.out.println(a1.hashCode());  //97
        System.out.println(a2.hashCode());  //97
        //false
        System.out.println(a1.equals(a2));
    }
}

可以看出 a1 和 a2 的hashCode值是一样的;但是,使用equals比较内容值是不一样的!就也就印证了定理的第一个小点

这也是发生Hash碰撞的原因!!!

再看看下面的例子

public class Demo {
    public static void main(String[] args) {
        UserEntity user1 = new UserEntity("Harmony", 1234);
        UserEntity user2 = new UserEntity("Harmony", 1234);

        System.out.println(user1 == user2);        // false
        System.out.println(user1.equals(user2));   // false
        System.out.println(user1.hashCode());      // 1163157884
        System.out.println(user2.hashCode());      // 1956725890
    }
}

这里使用 “==” 来判断对象,结果一定是false,因为user1和user2不是同一个对象,它们的内存地址是不同,使用 “==” 比较的就是内存地址!!!

为什么我们两个对象里面的内容值是一样的,而equals值却不相等呢??? 

判断equals() 的结果是false,两个hashCode的也是不相同的!

我们可以按住ctrl然后点击上面的equals()hashCode()方法跟进看一下,发现它们都是调用Object类的方法,如下

// Object 类
public native int hashCode();

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

第一个样例代码,跟进看发现,它们是调String类和Integer类hashCode()equals() 方法,显然在String类和Integer类中对这两个方法做了重写!!!

已String类为例 

// hashCode
public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

// equals
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

显然,这样子就很好解释了。这是因为我们没有对自己创建的UserEntity对象重写equals()和hashCode()方法!!!

UserEntity类 

public class UserEntity {

    private String userName;
    private Integer userId;

    public UserEntity(String userName, Integer userId) {
        this.userName = userName;
        this.userId = userId;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        UserEntity user = (UserEntity) obj;
        return user.userName.equals(userName) && user.userId.equals(userId);
    }

    @Override
    public int hashCode() {
        return userName.hashCode() + userId.hashCode();
    }
}

重新运行,结果如下: 

为什么要一起重写?

(1)Set 正常使用

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

Set 集合最大的特点:去重

但是当我们的Set集合里面存的是自定义对象呢?比如上述的UserEntity

Set<UserEntity> set = new HashSet<>();

显然,这样子让set失去了意义,在HashSet底层也是用hashCode判断是否是同一个元素! 

阿里巴巴Java开发手册 —— equals与hashCode问题

在阿里巴巴的Java开发手册中提到了如果我们要重写 equals 方法,就一定要把hashCode方法也一起重写了!!!


http://lihuaxi.xjx100.cn/news/176017.html

相关文章

Qt MainWindow窗口部件简介

Qt MainWindow窗口部件简介 1、菜单栏 特性如下&#xff1a; 有且仅有一个**位置&#xff1a;**顶部 // 创建菜单 最多只能有一个 QMenuBar * bar menuBar(); // 将菜单栏放入到窗口处 setMenuBar(bar);// 创建顶部菜单 QMenu * fileMenu bar->addMenu("文件&quo…

力扣刷题(代码回忆录)——动态规划

关于动态规划&#xff0c;你该了解这些&#xff01;动态规划&#xff1a;斐波那契数动态规划&#xff1a;爬楼梯动态规划&#xff1a;使用最小花费爬楼梯本周小结&#xff01;&#xff08;动态规划系列一&#xff09;动态规划&#xff1a;不同路径动态规划&#xff1a;不同路径…

linux进阶55——service文件

实现流程 创建.service文件 创建ping.service文件&#xff0c;内容可以如下&#xff1a; [Unit] Descriptionping daemon Afternetwork.target [Service] Restarton-failure ExecStart/usr/bin/ping 127.0.0.%d ExecReload/bin/kill -s -HUP $MAINPID ExecSt…

qmake language ~= 字符串替换操作 正则表达式

qmake language 的字符串替换操作的规则为&#xff1a; VAR ~ s[seprator]pattern[seprator]replace[seprator]?[gqi] 1、必须以s开头 2、seprator需要自己指定&#xff0c;可以是任意字符 3、pattern为正则表达式的内容&#xff0c;可以参看正则表达式规则&#xff1a;正则表…

商用短链平台_第4章_功能需求介绍和微服务拆分讲解

商用短链平台_第4章_功能需求介绍和微服务拆分讲解 文章目录商用短链平台_第4章_功能需求介绍和微服务拆分讲解第四章 商用短链平台-功能需求介绍和微服务拆分讲解第1集 学以致用-商用短链平台需求文档拆分和总结第2集 商用短链平台-微服务拆分和技术栈版本说明第3集 商用短链平…

[附源码]java毕业设计疫情防控期间网上教学管理

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

cubeIDE开发, stm32的OLED点亮及字符显示设计(基于SPI通信)

一、SPI 通信技术 显示屏&#xff08;LCD、OLED&#xff09;接口一般有I2C、SPI、UART、RGB、LVDS、MIPI、EDP和DP等。一般3.5寸以下的小尺寸LCD屏&#xff0c;显示数据量比较少&#xff0c;普遍采用低速串口&#xff0c;如I2C、SPI、UART。SPI&#xff08;Serial Peripheral I…

Linux——进程间通信(共享内存)

一、共享内存 1、定义 共享内存为多个进程之间共享和传递数据提供了一种有效的方式。共享内存是先在物理内存上申请一块空间&#xff0c;多个进程可以将其映射到自己的虚拟地址空间中。所有进程都可以访问共享内存中的地址&#xff0c;就好像它们是由malloc分配的一样。如果某…