Object类 equals方法 hashCode方法 集合

Object类

Object类是所有类的父类,所以:

  1. Object的类的成员变量和成员方法,其余的类会继承,可以使用在这里插入图片描述

  2. Object类可以使用多态创建任意对象,同时拥有子类的重写方法

在这里插入图片描述
我们先假设子类重写了equals方法和hashCode方法(IDEA默认的重写)

public class Human {private String name;private int age;public void setName(String name) {this.name = name;}public String getName() {return name;}public void setAge(int age) {this.age = age;}public int getAge() {return age;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (!(o instanceof Human)) return false;Human human = (Human) o;if (getAge() != human.getAge()) return false;return getName() != null ? getName().equals(human.getName()) : human.getName() == null;}@Overridepublic int hashCode() {int result = getName() != null ? getName().hashCode() : 0;result = 31 * result + getAge();return result;}
}

此时我们调用equals方法

public class ObjectTest {public static void main(String[] args) throws Throwable {Object object = new Human();Human human = new Human();System.out.println(object.equals(human));}
}

如果这里的equals使用的Object类的equals方法,肯定是false

在这里插入图片描述

但是实际上的执行结果是true
在这里插入图片描述

说明已经调用了子类重写之后的方法

equals方法

本质上是为了判断两个引用类型的对象/两个基本类型的数值是否相等

equals用于基本类型

判断两个数值是否相等

        char a = 'a';byte aa = 97;System.out.println(a == aa);// 这里发生了自动类型提升,char和byte都可以无损失地转为int,a的ascii值就是97,所以是true

结果是true

equals用于引用类型

        Human h1 = new Human();h1.setAge(1);h1.setName("rainbowSea");Human h2 = new Human();h2.setAge(1);h2.setName("rainbowSea");System.out.println(h1.equals(h2));

上面讲了,这里重写了,比较的是age和name的值是否相等,所以是true

如果不重写,比较的就是内存地址,必然是false

如何重写equals方法

但是怎么重写,其实大有讲究,我们以String类重写的equals方法以及Human类重写的equals方法为例,展开说说

String类的equals方法:

/*** Compares this string to the specified object.  The result is {@code* true} if and only if the argument is not {@code null} and is a {@code* String} object that represents the same sequence of characters as this* object.** @param  anObject*         The object to compare this {@code String} against** @return  {@code true} if the given object represents a {@code String}*          equivalent to this string, {@code false} otherwise** @see  #compareTo(String)* @see  #equalsIgnoreCase(String)*/public boolean equals(Object anObject) {// 先比较两个对象是否是同一个对象(即内存地址是否相等)if (this == anObject) {// 若相等,直接返回truereturn true;}// 再比较两个对象的字符串值,这一步有很多技巧// 1. 先判断这个对象是不是String类的对象,不是直接返回falseif (anObject instanceof String) {// 2. 再判断两个字符串长度是否相等,不相等返回falseString anotherString = (String)anObject;int n = value.length;if (n == anotherString.value.length) {// 3. 是String对象,而且字符串长度也相等,没办法了,只能判断字符串内容了char v1[] = value;char v2[] = anotherString.value;int i = 0;while (n-- != 0) {// 不用比较全部的字符串,而是从头到尾逐个对比,发现不同直接返回falseif (v1[i] != v2[i])return false;i++;}// 内容相同,返回truereturn true;}}// 不是String类的对象return false;}

三部分筛选,环环相扣,逻辑严谨(写在前面的判断一定是后面判断的前提),代码简洁

强烈建议每一位读者多去读源码,不管是什么项目的,JDK,MySQL,Spring,MyBatis……都可以读,非常有帮助!

IDEA自动生成的equals方法

    @Overridepublic boolean equals(Object o) {// 1. 和String一样,先判断是不是相同引用if (this == o) return true;// 2. 再判断是不是这个类的对象if (!(o instanceof Human)) return false;Human human = (Human) o;// 3. 先比较年龄,再比较姓名,使用三元运算符简化代码if (getAge() != human.getAge()) return false;return getName() != null ? getName().equals(human.getName()) : human.getName() == null;}

hashCode方法

HashSet集合

可以用于去重,存取顺序不同

HashSet集合的add方法

HashSet可以去重,主要就是靠add方法实现

    public static void main(String[] args) throws Throwable {HashSet<Integer> hashSet = new HashSet<>();hashSet.add(1);hashSet.add(1);System.out.println(hashSet);}

在这里插入图片描述
只有一个1,说明去重了

在调用HashSet的add方法时,会先进行判断,如果add的对象之前添加过,就不会添加进去,以此达到去重的目的

那么add方法是通过何种办法判断对象相等?它和hashCode方法又有什么关系呢?

源码讲解

add方法调用了put方法
在这里插入图片描述

put方法又调用了putVal,putVal又调用了hash()
在这里插入图片描述

hash又调用了hashCode方法,null统一都是0,所以只能插入一次;不是null,则让对象的哈希值与哈希值无符号右移16位后的值进行异或运算,返回运算后的结果
在这里插入图片描述
hashCode方法不能再往下点了,native表示用C++实现
在这里插入图片描述

看到这里我们可以大胆猜测,add方法能去重,就是因为每次添加的时候都会计算添加对象的hashCode方法的哈希值,如果计算的哈希值相等,就说明这是同一个对象,就不予添加;反之,则添加

hashCode可以将任何一个对象转化为一个int类型的值,相同的对象会转换位相同的哈希值,不同的对象会被映射成不同的哈希值

为什么重写equals方法后,一定要重写hashCode方法

Java当中比较对象/数值是否相等,一般有3种:==,equals,hashCode

==

==没什么好说的,比较基本类型数值的时候还算是有用,比较引用类型是否相等,就非常的强硬,只能比较地址,意思就是“如果两个对象不是同一个对象,那么这两个对象就是不相等的

但是我们实际生活中,定义两个东西是不是相同的,概念没有这么严格,我买了两个盒装的13600kf的CPU,他们价格一样,性能参数一样,甚至体质和超频潜力都是一模一样的,尽管他们并不是同一个CPU,但是我们基本上都认为这两个CPU是“等价”的

其他的例子还有很多,在这里不再赘述

由此,我们引出了重写的equals方法

equals方法

只能用于引用类型的对象,不能用于基本类型

刚才说了,我们现实生活中,不会有这么严格的比较相等的标准,而重写Object的equals方法,就满足了我们的这个需求。当两个对象是同一个类的对象,并且所有的成员变量的属性值都是相等的,那么我们就可以认为,这两个对象是相等的

重写的equals方法,重写逻辑都是相同的:

  1. 是否是同一个对象引用
  2. 是否是同一个类型
  3. 逐个比较属性值是否相等

hashCode方法

既可以用于基本类型,也可以用于引用类型

hashCode源码简单分析

get_next_hash() 方法会根据 hashCode 的取值来决定采用哪一种哈希值的生成策略。
默认的hashCode为5
1~5分别是:随机数,根据内存指针计算,恒定2,自增,xor-shift算法

static inline intptr_t get_next_hash(Thread * Self, oop obj) {intptr_t value = 0 ;if (hashCode == 0) {// 返回随机数value = os::random() ;} elseif (hashCode == 1) {//用对象的内存地址根据某种算法进行计算intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;} elseif (hashCode == 2) {// 始终返回1,用于测试value = 1 ;            } elseif (hashCode == 3) {//从0开始计算哈希值value = ++GVars.hcSequence ;} elseif (hashCode == 4) {//输出对象的内存地址value = cast_from_oop<intptr_t>(obj) ;} else {// 默认的hashCode生成算法,利用xor-shift算法产生伪随机数unsigned t = Self->_hashStateX ;t ^= (t << 11) ;Self->_hashStateX = Self->_hashStateY ;Self->_hashStateY = Self->_hashStateZ ;Self->_hashStateZ = Self->_hashStateW ;unsigned v = Self->_hashStateW ;v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;Self->_hashStateW = v ;value = v ;}value &= markOopDesc::hash_mask;if (value == 0) value = 0xBAD ;assert (value != markOopDesc::no_hash, "invariant") ;TEVENT (hashCode: GENERATE) ;return value;
}

未被重写的hashCode方法,使用的就是5,也就是xor-shift算法,去生成伪随机数的哈希值

Marsaglia’s xor-shift 随机数生成法是一种快速并且散列性好的哈希算法

参考文章
Object中的hashCode()终于搞懂了!!!

9.1 xorshift算法

JDK核心JAVA源码解析(9) - hashcode 方法

默认hashCode全局变量的值是5,也就是说会走到这里

在这里插入图片描述

我们不需要看懂它在干什么,只需要知道一件事情:
它对相同的输入,一定是相同的输出;不同的输入,大概率是不同的输出

不知道为什么是大概率的读者可以自行搜索哈希算法,哈希碰撞,这里只做简单解释:

用有限的输出去映射无限的输入,只要输入的足够多,一定会发生哈希碰撞。

但是我们可以优化算法,尽量降低哈希碰撞在小数据量时候的发生的概率

讲了这么多,我们回归正题,hashCode也是一种判断是否相等的办法,而且非常高效,至少比equals快得多,但是hashCode方法不靠谱,有时候不相等也会认为是相等的

结论

关于重写equals方法,为什么一定要重写hashCode方法

hashCode计算虽然非常快,但是不靠谱,所以我们一般采用以下策略,去判断两个对象是否相等(这样效率很高):

  1. hashCode如果不相等,那么一定不是同一个对象,直接return false
  2. hashCode如果相等,再去调用equals方法,如果equals方法结果不相等,返回false
  3. 但是如果equals方法显示也相等,就说明出现了哈希碰撞,此时就需要在哈希表的数组对应位置生成链表;链表过长的时候还会转成红黑树,加快查询效率

所以这个方法重写的关联关系,本质上是因为人们约定俗成的一个规定:

equals方法相等,说明两个对象相等;两个对象相等,则hashCode一定相等;
hashCode不相等,则肯定不是相等的对象

equals方法是保底机制,但是比较慢

使用这种比较机制,可以大大加快哈希表数据结构key的地址的计算

如果只重写equals方法,不重写hashCode方法,就会出现“用户认为是相等的对象,hashCode值却不相等

所以我们才要一起重写hashCode方法

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

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

相关文章

Flink 实时数仓(一)【实时数仓离线数仓对比】

前言 昨天技术面的时候&#xff0c;面试官说人家公司现在用的都是最新的技术&#xff0c;比如 Doris 等一些最新的工具&#xff0c;确实这些课是学校永远不会开设的&#xff0c;好在他说去了会带着我做一做。可是 ...... 学院舍不得让走啊 ...... 没办法&#xff0c;情况就是这…

python代码报错requests.exceptions.SSLError

直接浏览器访问报ssl错误的网站&#xff0c;然后找到证书&#xff0c;选择导出 然后在cmd里面执行&#xff0c;去查看certifi的位置 python -c "import certifi;print(certifi.where())"找到之后用文本编辑器打开&#xff0c;我用的vscode 滚动到最后面&#xff0c…

什么是重放攻击(Reply attack)?

什么是重放攻击(Reply attack)? 重放攻击&#xff0c;也称为回放攻击&#xff0c;是一种网络攻击方式。重放攻击是一种中间人攻击&#xff0c;攻击者通过截获合法的数据传输并重新发送它们来欺骗接收方&#xff0c;让接收方误以为是合法的消息。重放攻击是非常常见的&#xf…

在IDEA中使用.env文件导入系统配置的图文教程

JetBrains的IDEA是一款功能强大的集成开发环境&#xff0c;为开发人员提供了丰富的功能和工具。使用.env文件来管理配置信息在IDEA中非常简单。 旧版本默认支持&#xff0c;新版本idea需要安装插件才可以。 这里我们可以安装EnvFile插件&#xff0c;步骤如下&#xff1a; 在弹…

详解数仓的向量化执行引擎

前言 适用版本&#xff1a;【基线功能】 传统的行执行引擎大多采用一次一元组的执行模式&#xff0c;这样在执行过程中CPU大部分时间并没有用来处理数据&#xff0c;更多的是在遍历执行树&#xff0c;就会导致CPU的有效利用率较低。而在面对OLAP场景巨量的函数调用次数&#x…

ctfshow web29-web40

命令执行 看清都过滤了些什么&#xff01;&#xff01; 知识点&#xff1a; web34&#xff1a;当;和()被过滤了就用语言结构&#xff0c;一般有echo print isset unset include require web37&#xff1a;data协议是将后面的字符串当成php代码执行&#xff0c;例如 /?cdat…

JVM学习笔记(四)类加载与字节码技术

目录 一、类文件结构 二、字节码指令 2.3 图解方法执行流程 1&#xff09;原始 java 代码 2&#xff09;编译后的字节码文件 3&#xff09;常量池载入运行时常量池 4&#xff09;方法字节码载入方法区 5&#xff09;main 线程开始运行&#xff0c;分配栈帧内存 6&…

甘特图是什么?如何利用其优化项目管理流程?

甘特图是项目管理软件中十分常见的功能&#xff0c;可以说每一个项目经理都要学会使用甘特图才能更好的交付项目。什么是甘特图&#xff1f;甘特图用来做什么&#xff1f;简单来说一种将项目任务与时间关系直观表示的图表&#xff0c;直观地展示了任务进度和持续时间。 一、甘特…

【C++】string类的增删改查模拟实现(图例超详细解析!!!)

目录 一、前言 二、string类的模拟实现 ✨前情提要 ✨Member functions —— 成员函数 ⚡构造函数 ⚡拷贝构造函数 ⚡赋值运算符重载 ⚡析构函数 ✨Element access —— 元素访问 ⚡operator[ ] ⚡Iterator —— 迭代器 ✨Capacity —— 容量 ⚡size ⚡capacity ⚡clea…

井字棋源码(网络线程池版)

源码链接&#xff1a;game 效果可能没有那么好&#xff0c;大家可以给点建议。 效果展示 game.h #include <stdio.h> #include <stdlib.h> #include <time.h>#define ROW 3 #define COL 3void InitBoard(char board[ROW][COL], int row, int col) {int i…

企业数字化转型,“业务”先行

在当今时代&#xff0c;数字化转型已经成为企业发展的必经之路。数字化转型&#xff0c;简而言之&#xff0c;就是运用数字技术&#xff0c;对企业运营管理的各个环节进行深度改造&#xff0c;以提升企业的运营效率和市场竞争力。据有关机构研究测算&#xff0c;数字化转型可使…

丈母娘眼中“靠谱女婿”职业榜曝光,公务员跌落榜首,新兴职业成宠儿!

正如婆婆和媳妇相处很复杂&#xff0c;丈母娘和女婿亦有着微妙关系&#xff0c;看对眼是“半个儿”&#xff0c;不对付则会成为小两口婚姻的“地雷”&#xff0c;甚至是恋爱路上的“拦路虎”。 近来&#xff0c;最新丈母娘认可的“靠谱女婿”职业榜排行新鲜出炉&#xff0c;备受…

5g工业数采网关是什么?-天拓四方

随着工业4.0时代的到来&#xff0c;数字化、网络化、智能化成为工业发展的新趋势。在这个过程中&#xff0c;5G工业数采网关作为一种关键设备&#xff0c;发挥着越来越重要的作用。本文将详细解析5G工业数采网关是什么&#xff0c;以及它在工业领域中的应用和重要性。 一、5G工…

socket套接字在tcp客户端与tcp服务器之间的通信,以及socket中常用的高效工具epoll

1.socket&#xff08;套接字&#xff09;的概念 Socket是对TCP/IP协议的封装&#xff0c;Socket本身并不是协议&#xff0c;而是一个调用接口&#xff08;API&#xff09;&#xff0c;通过Socket&#xff0c;我们才能使用TCP/IP协议,主要利用三元组【ip地址&#xff0c;协议&am…

【芯片科普】运算放大器用作比较器的注意事项

运算放大器和比较器 比较器和运算放大器电气符号非常相像&#xff0c;都是有反相、同相两个输入端和一个输出端的器件&#xff0c;输出端的输出电压范围一般在供电的轨到轨之间&#xff1b;同时比较器和运算放大器都具有低偏置电压、高增益和高共模抑制比的特点。 图1 运算放…

自由场、半自由场、扩散场

按声场性质可以将声场分为三类&#xff1a;自由声场、半自由声场、扩散声场 分别对应着全消声室&#xff0c;半消声室&#xff0c;混响室 自由声场&#xff1a; 声源在均匀、各向同性媒介中传播时&#xff0c;不计边界影响的声场&#xff0c;此时声场中只有直达声没有反射声。…

测试工程师面试准备(软硬件)

您好&#xff0c;我叫XXX。学历XX&#xff0c;XXX专业毕业。X年X月份毕业&#xff0c;但是去年二月份已经找到工作开始实习了&#xff0c;目前工作一年了&#xff0c;这一年的过程中我主要负责软件的开发和测试和软硬件联调测试工作。具体来说就是&#xff0c;在软件开发完成后…

华为 2024 届实习校园招聘-硬件通⽤/单板开发——第八套

华为 2024 届实习校园招聘-硬件通⽤/单板开发——第八套 部分题目分享&#xff0c;完整版带答案(有答案和解析&#xff0c;答案非官方&#xff0c;未仔细校正&#xff0c;仅供参考&#xff09;&#xff08;共十套&#xff09;获取&#xff08;WX:didadidadidida313&#xff0c…

Qwen1.5微调

引子 由于工作上需要&#xff0c;一直在用Qwen做大模型推理&#xff0c;有个再训练的需求&#xff0c;特此琢磨下Qwen的训练。OK&#xff0c;我们开始吧。 一、安装环境 查看显卡驱动版本 根据官网推荐 OK&#xff0c;docker在手&#xff0c;天下我有。 docker pull qwenll…

rancher-rke2 修改--service-cluster-ip-range

一、场景 因为需要部署新版本的ingress-nginx&#xff0c;而部署ingress-nginx的时候需要使用hostnetowrk以及nodeport的端口为80和443&#xff0c;service-node-port-range 默认为30000开始,部署会报错。 二、产生修改的需求 1、api-servier的配置文件位置 默认是没有的&…