两个对象相等(==、equals、hashCode)详解

news2024/11/26 4:36:54

目录

  • 1. == 和 equals
  • 2. hashCode
    • 1. hash 概述
    • 2. hashCode
      • 1. 概念
      • 2. 获取对象地址
  • 3. hashCode 与 equals
    • 1. 两者关系
    • 2. 重写 equals并 重写 hashCode
      • 1. 只重写 equals
      • 2. 重写 equals 并重写 hashCode
    • 3. 小结

1. == 和 equals

在 Java 中,判断两个对象是否相等,使用的是 ==equals

==equals 的区别:

  • == 判断两对象相等,是判断它们的地址是否相等

  • equals 未被重写,也是通过判断地址来判断相等,等同于 ==

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

    但,当我们的业务有特殊需求的时候,往往会 重写 equals,如:String 中的 equals 就被重写

        // 地址相等 => 相等
        // 字符串内容相同 => 相等
        public boolean equals(Object anObject) {
            if (this == anObject) {
                return true;
            }
            if (anObject instanceof String) {
                String anotherString = (String)anObject;
                // String底层是用 char value[]存储字符的
                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;
        }
    

2. hashCode

1. hash 概述

hash: 散列算法,用来标志 文件/数据 的唯一性,相当于一种指纹。

  • 特点:
    • 正向快速:明文 + hash算法 => 快速获取 hash值
    • 逆向困难:几乎不可能根据给定 hash值 推导出密文
    • 输入敏感:原始输入信息轻微不同就会导致 hash 值 有很大的区别
    • 避免冲突:两段不同的明文几乎不可能出现相同的hash值

hash表: 存储 hash值 。

2. hashCode

1. 概念

hashCode 并不是对象的地址!!!

  • 每个对象都有一个 hashCode,其代表的是,对象存入散列表(HashTableHashMapHashSet)的地址
  • 只有要将对象存入 散列表 的时候才会用到 hashCode

获取 hashCode :

        Object obj = new Object();
        System.out.println(obj);
        int hashCode = obj.hashCode();
        System.out.println("hashCode: " + hashCode);
        System.out.println("hashCode16进制: " + Integer.toHexString(hashCode));
        System.out.println("使用最初的hashCode算法:" + System.identityHashCode(hashCode));
java.lang.Object@1b6d3586
hashCode: 460141958
hashCode16进制: 1b6d3586
使用最初的hashCode算法:1163157884

通过 JVM Options 指定 hashCode 的生成策略: N的取值在 [0,4]之间的随机整数,当N不在此区间,就会使用默认的生成策略。

-XX:hashCode=N

2. 获取对象地址

既然已经知道 hashCode 与对象的存储地址没关系,那么我们想要获取对象地址咋办?

  • 引入依赖:

    <dependency>
        <groupId>org.openjdk.jol</groupId>
        <artifactId>jol-core</artifactId>
        <version>0.16</version>
    </dependency>
    
  • VM.current().addressOf(obj) 获取对象地址

            Object obj = new Object();
            String str = new String("123");
            long strAddress = VM.current().addressOf(str);
            long objAddress = VM.current().addressOf(obj);
    

3. hashCode 与 equals

1. 两者关系

重写了 equals,不一定要重写 hashCode !!!

只有要将对象存入 散列表 ,并且已经重写了 equals,才需要重写 hashCode !!!

在 散列表 中,两个对象相等(equals 返回 true),那么他们的 hashCode 一定相同


先来简单回顾一下 HashMap 存储 key-value 的过程:

  • 计算 hash 值:key.hashCode()^( key.hashCode() >>>16 ) 计算出hash值
  • 如果 没发生hash碰撞(两个对象的hash值相同),就把对象直接存入桶中
    如果 发生了hash碰撞,就判断 key 是否相等,并使用 链表/红黑树 进行数据处理

HashMap 中判断两个对象相等的条件时:key相等 且 value相等

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }

所以,在计算对象的hash值,并将其装入散列表的时候,需要计算其 key的 hashCode ,也需要计算其 value的 hashCode

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

通过参考 HashMap 重写 HashMapHashMap 的思路,我们就能自己重写这两个方法。

2. 重写 equals并 重写 hashCode

1. 只重写 equals

我们自定义对象 User,重写了其 equals (使得 name 相等,两个对象就相等)。

代码:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private String name;
    private Integer age;

    /**
     * @Description 名字相同也返回true
     * @param obj
     * @return boolean
    */
    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof User)) return false;
        if (obj == this) return true;
        User user = (User) obj;
        return (user.name .equals(this.name));
    }

    public static void main(String[] args) {
        Set<Object> set = new HashSet<>();
        User user1 = new User("张三", 3);
        User user2 = new User("张三", 4);
        User user3 = new User("李四", 5);
        set.add(user1);
        set.add(user2);
        set.add(user3);

        System.out.println(user1.equals(user2));
        System.out.println(set);
        set.forEach( item -> {
            System.out.print(item.hashCode() + " ");
        });
    }
}

输出:

分析:

  • HashSet 不能装重复对象,而 user1user2 是同一对象(equals 返回 true),但是两者都被装入了 HashSet,这逻辑明显就是错误的
  • 这就是 equals 返回 true 的范围 > hashCode 相等的范围 导致的

2. 重写 equals 并重写 hashCode

在上述案例的基础上,重写一下 hashCode

代码:

    @Override
    public int hashCode() {
        return name.hashCode();
    }

输出:

分析:

  • user1user2 是同一对象(equals 返回 true),所以,只装入了 user1
  • 这就是重写了两方法之后, equals 返回 true 的范围 = hashCode 相等的范围的结果

那么,我判断对象相等,需要其 nameage 都相等呢?

先重写 equals

    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof User)) return false;
        if (obj == this) return true;
        User user = (User) obj;
        return (user.name .equals(this.name) && user.age == this.age);
    }

hashCode 应该这样写:

    @Override
    public int hashCode() {
        return name.hashCode() ^ age.hashCode();
    }

main 方法:

    public static void main(String[] args) {
        Set<Object> set = new HashSet<>();
        User user1 = new User("张三", 3);
        User user2 = new User("张三", 1);
        User user3 = new User("李四", 5);
        User user4 = new User("李四", 5);
        set.add(user1);
        set.add(user2);
        set.add(user3);
        set.add(user4);

        System.out.println(user1.equals(user2));
        System.out.println(user3.equals(user4));
        System.out.println(set);
        set.forEach( item -> {
            System.out.print(item.hashCode() + " ");
        });
    }

输出:

3. 小结

  • 重写了 equals 不一定要重写 hashCode
    • 在没有使用 散列表(HashTableHashMapHashSet)的时候,hashCode 就是一串没有人调用的整数
  • 在 散列表 中,两个对象相等(equals 返回 true),那么他们的 hashCode 一定相同
    • 一旦使用了散列表,并且重写了 equals,那么就必须重写 hashCode

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/48346.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CMOS反相器的工作原理和电路结构

CMOS 反相器的电路结构 当输入为高电压的时候&#xff0c;下半部分导通&#xff0c;输出端接地 当输入为低电压的时候&#xff0c;上半部分导通&#xff0c;输出端连接VDD 静态输入特性 从反相器输入端看进去输入电压与电流的关系 因为栅极和衬底之间存在着以二…

子网掩码与VLAN有何区别?

子网掩码与VLAN有何区别?_百度知道 (baidu.com) 可以这么理解&#xff1a;子网掩码是对节省IP地址的资源而设立。而VLAN则是对方便网络管理需要而设立。两者之间似乎有相识之处&#xff0c;但各自的功能是不一样的。 vlan下起子网,子网掩码与vlan有何区别-天道酬勤-花开半夏…

[附源码]Python计算机毕业设计Django的高校课程知识库

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

前后端分离项目的https加密解密过程一、从https说起

前后端分离项目的https加密解密过程一、从https说起 最近在看一个开源的项目&#xff0c;叫MetersPhere&#xff0c;在看的过程中&#xff0c;发现并不了解其中加密解密的过程&#xff0c;打算将整个流程梳理一下。 从https一定是安全的么说起 对称加密和非对称加密 一&…

PC_访存过程@内存地址翻译过程@具有快表TLB和cache的多级存储系统

文章目录PC_访存过程内存地址翻译过程具有快表TLB和cache的多级存储系统具有TLB和Cache的多级存储系统三类缺失的可能情况组合小结cache缺失/内存缺页处理机构带TLB虚拟存储器的cpu访存过程PC_访存过程内存地址翻译过程具有快表TLB和cache的多级存储系统 具有TLB和Cache的多级…

POI的使用

POI简介&#xff08;Apache POI&#xff09;&#xff0c;Apache POI是Apache软件基金会的开放源码函式库&#xff0c;POI提供API给Java程序对Microsoft Office格式档案读和写的功能。 Apache POI官网 HSSF &#xff0d; 提供读写Microsoft Excel格式档案的功能。&#xff08;.…

C++ bool类型变量赋值true,输出结果却是false?是因为cin输入的true会被当成字符串,所以bool变量原值不变吗?

首先&#xff0c;大家可能看过其他文章&#xff0c;他们给出的观点是这样的&#xff1a; 在C中bool类型的变量初始值为false&#xff0c;所以如果你不初始化&#xff0c;那么对变量使用cin>>赋值true和false的时候&#xff0c;编译器会把true和false当成是字符串&#x…

【LeetCode每日一题:895.最大频率栈~~~Map+栈】

题目描述 设计一个类似堆栈的数据结构&#xff0c;将元素推入堆栈&#xff0c;并从堆栈中弹出出现频率最高的元素。 实现 FreqStack 类: FreqStack() 构造一个空的堆栈。 void push(int val) 将一个整数 val 压入栈顶。 int pop() 删除并返回堆栈中出现频率最高的元素。 如果…

[附源码]Python计算机毕业设计Django的酒店预订系统设计与实现

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

深度学习快速入门----Pytorch 系列3

注&#xff1a;参考B站‘小土堆’视频教程 视频链接&#xff1a;【PyTorch深度学习快速入门教程&#xff08;绝对通俗易懂&#xff01;&#xff09;【小土堆】 系列文章&#xff1a; 深度学习快速入门----Pytorch 系列1 深度学习快速入门----Pytorch 系列2 深度学习快速入门--…

[附源码]计算机毕业设计JAVA小区供暖收费管理系统

[附源码]计算机毕业设计JAVA小区供暖收费管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM my…

世界杯小组赛频繁爆冷?这或许是强队的谋略 一分钟带你了解2022卡塔尔世界杯赛制

今年的世界杯你赚到钱了吗&#xff1f; 我们这里所说的世界杯是指世界杯决赛圈&#xff0c;也被叫做世界杯正赛。参赛队伍是已经通过世界杯预选赛选拔出的的32支队伍&#xff08;除了东道主卡塔尔自动晋级&#xff09;。 世界杯中没有皇马、巴萨、曼联&#xff0c;这些都是俱乐…

基于 SpringBoot + MyBatis 的网页版五子棋对战

目录 一、项目所要实现的功能模块 1、用户模块 2、匹配模块 3、对战模块 二、使用技术 三、项目截图 1、登录页面 2、注册页面 3、游戏大厅页面 4、游戏房间页面 四、创建 SpringBoot 项目 1、在 IDEA 中创建一个 SpringBoot 项目 2、设置项目名称 3、选择项目依…

第2-4-9章 规则引擎Drools实战(2)-信用卡申请

文章目录9.2 信用卡申请9.2.1 计算规则9.2.2 实现步骤9.2 信用卡申请 全套代码及资料全部完整提供&#xff0c;点此处下载 本小节我们需要通过Drools规则引擎来根据规则进行申请人的合法性检查&#xff0c;检查通过后再根据规则确定信用卡额度&#xff0c;最终页面效果如下&a…

浅谈架构备考.补缺.V1

2022.11.28 可靠性分析 to repair to failure between failure 平均故障间隔时间。平均故障间隔时间&#xff08;Mean Time Between Failure&#xff0c;MTBF&#xff09;常常与 MTTF 发生混淆。 因为两次故障&#xff08;失败&#xff09;之间必然有修复行为&#xff0c;因…

SpringBoot主启动类使用@ComponentScans、@ComponentScan扫描组件类,注意避坑

前言&#xff1a; 1、大家都知道&#xff0c;Springboot主启动加载会默认扫描同级包目录下所有的组件类、配置类&#xff0c;然后进行解析注入到Spring容器中。SpringBootApplication 是个联合注解&#xff0c;里面包含了 ComponentScan 组件扫描注解&#xff0c;所以我们不需要…

沉睡者IT - 什么是NFT?

欢迎关注沉睡者IT&#xff0c;点上面关注我 ↑ ↑ NFT&#xff0c;全称为Non-Fungible Token&#xff0c;指非同质化通证&#xff0c;实质是区块链网络里具有唯一性特点的可信数字权益凭证&#xff0c;是一种可在区块链上记录和处理多维、复杂属性的数据对象。 以上是百度百科…

MongoBd 离线安装与管理

背景&#xff1a; 鉴于内部网络原因&#xff0c;可能一个简单的操作变得复杂化&#xff0c;现在就Mongodb的离线安装分享本人的操作经验: 材料&#xff1a; 操作系统&#xff1a;centos7.6 MongoDB(主程序) : mongodb-linux-x86_64-rhel70-6.0.1.tgz 下载地址&#xff1a;下载…

传输层协议 —— UDP

目录 一、端口号的划分范围 二、认识知名端口号 三、两个问题 四、nestat和pidof命令 五、UDP协议 1. UDP首部格式 2. UDP的特点 3. 面向数据报 4. UDP的缓冲区 5. UDP使用注意事项 6. 基于UDP的应用层协议 一、端口号的划分范围 端口号的长度是16位&#xff0c;因此…

博途PLC和MATLAB矩阵运算存储方法对比

MATLBA不用多说,号称矩阵实验室可想而知在MATLAB里对矩阵的存储、运算非常简单、高效。如下图简单定义一个5*3的矩阵 1、rand(5*3) 上面利用rand()函数简单的实现了内存矩阵存储空间分配+附随机初值,下面我们看下博途里的矩阵定义存储方法。 BP神经网络PID算法的PLC实现过程…