equals()与hashCode()方法详解

java.lang.Object类中有两个非常重要的方法:

1

2

public boolean equals(Object obj)

public int hashCode()

Object类是类继承结构的基础,所以是每一个类的父类。所有的对象,包括数组,都实现了在Object类中定义的方法。

回到顶部

equals()方法详解

equals()方法是用来判断其他的对象是否和该对象相等.

  equals()方法在object类中定义如下: 

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

很明显是对两个对象的地址值进行的比较(即比较引用是否相同)。但是我们知道,String 、Math、Integer、Double等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法。

  比如在String类中如下:

复制代码

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

复制代码

很明显,这是进行的内容比较,而已经不再是地址的比较。依次类推Math、Integer、Double等这些类都是重写了equals()方法的,从而进行的是内容的比较。当然,基本类型是进行值的比较。

它的性质有:

  • 自反性(reflexive)。对于任意不为null的引用值x,x.equals(x)一定是true

  • 对称性(symmetric)。对于任意不为null的引用值xy,当且仅当x.equals(y)true时,y.equals(x)也是true

  • 传递性(transitive)。对于任意不为null的引用值xyz,如果x.equals(y)true,同时y.equals(z)true,那么x.equals(z)一定是true

  • 一致性(consistent)。对于任意不为null的引用值xy,如果用于equals比较的对象信息没有被修改的话,多次调用时x.equals(y)要么一致地返回true要么一致地返回false

  • 对于任意不为null的引用值xx.equals(null)返回false

对于Object类来说,equals()方法在对象上实现的是差别可能性最大的等价关系,即,对于任意非null的引用值xy,当且仅当xy引用的是同一个对象,该方法才会返回true

需要注意的是当equals()方法被override时,hashCode()也要被override。按照一般hashCode()方法的实现来说,相等的对象,它们的hash code一定相等。

回到顶部

hashcode() 方法详解

hashCode()方法给对象返回一个hash code值。这个方法被用于hash tables,例如HashMap。

它的性质是:

  • 在一个Java应用的执行期间,如果一个对象提供给equals做比较的信息没有被修改的话,该对象多次调用hashCode()方法,该方法必须始终如一返回同一个integer。

  • 如果两个对象根据equals(Object)方法是相等的,那么调用二者各自的hashCode()方法必须产生同一个integer结果。

  • 并不要求根据equals(java.lang.Object)方法不相等的两个对象,调用二者各自的hashCode()方法必须产生不同的integer结果。然而,程序员应该意识到对于不同的对象产生不同的integer结果,有可能会提高hash table的性能。

大量的实践表明,由Object类定义的hashCode()方法对于不同的对象返回不同的integer。

在object类中,hashCode定义如下:

public native int hashCode();

 说明是一个本地方法,它的实现是根据本地机器相关的。当然我们可以在自己写的类中覆盖hashcode()方法,比如String、Integer、Double等这些类都是覆盖了hashcode()方法的。例如在String类中定义的hashcode()方法如下:

复制代码

public int hashCode() {  int h = hash;  if (h == 0) {  int off = offset;  char val[] = value;  int len = count;  for (int i = 0; i < len; i++) {  h = 31 * h + val[off++];  }  hash = h;  }  return h;  
}  

复制代码

解释一下这个程序(String的API中写到):s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
      使用 int 算法,这里 s[i] 是字符串的第 i 个字符,n 是字符串的长度,^ 表示求幂(空字符串的哈希码为 0)。

       想要弄明白hashCode的作用,必须要先知道Java中的集合。  
       总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。这里就引出一个问题:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?
        这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。   
       于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上,初学者可以简单理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。  
       这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。  

 简而言之,在集合查找时,hashcode能大大降低对象比较次数,提高查找效率!

Java对象的eqauls方法和hashCode方法是这样规定的:

1、相等(相同)的对象必须具有相等的哈希码(或者散列码)。

2、如果两个对象的hashCode相同,它们并不一定相同。

 以下是Object对象API关于equal方法和hashCode方法的说明:

  • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
  • It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
  • 以上API说明是对之前2点的官方详细说明

关于第一点,相等(相同)的对象必须具有相等的哈希码(或者散列码),为什么?

 想象一下,假如两个Java对象A和B,A和B相等(eqauls结果为true),但A和B的哈希码不同,则A和B存入HashMap时的哈希码计算得到的HashMap内部数组位置索引可能不同,那么A和B很有可能允许同时存入HashMap,显然相等/相同的元素是不允许同时存入HashMap,HashMap不允许存放重复元素。

 关于第二点,两个对象的hashCode相同,它们并不一定相同

 也就是说,不同对象的hashCode可能相同;假如两个Java对象A和B,A和B不相等(eqauls结果为false),但A和B的哈希码相等,将A和B都存入HashMap时会发生哈希冲突,也就是A和B存放在HashMap内部数组的位置索引相同这时HashMap会在该位置建立一个链接表,将A和B串起来放在该位置,显然,该情况不违反HashMap的使用原则,是允许的。当然,哈希冲突越少越好,尽量采用好的哈希算法以避免哈希冲突。

 所以,Java对于eqauls方法和hashCode方法是这样规定的:     

   1.如果两个对象相同,那么它们的hashCode值一定要相同;

      2.如果两个对象的hashCode相同,它们并不一定相同(这里说的对象相同指的是用eqauls方法比较)。  
        如不按要求去做了,会发现相同的对象可以出现在Set集合中,同时,增加新元素的效率会大大下降。
      3.equals()相等的两个对象,hashcode()一定相等;equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。

        换句话说,equals()方法不相等的两个对象,hashcode()有可能相等(我的理解是由于哈希码在生成的时候产生冲突造成的)。反过来,hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。

        在object类中,hashcode()方法是本地方法,返回的是对象的地址值,而object类中的equals()方法比较的也是两个对象的地址值,如果equals()相等,说明两个对象地址值也相等,当然hashcode()也就相等了;在String类中,equals()返回的是两个对象内容的比较,当两个对象内容相等时,Hashcode()方法根据String类的重写代码的分析,也可知道hashcode()返回结果也会相等。以此类推,可以知道Integer、Double等封装类中经过重写的equals()和hashcode()方法也同样适合于这个原则。当然没有经过重写的类,在继承了object类的equals()和hashcode()方法后,也会遵守这个原则。

回到顶部

Hashset、Hashmap、Hashtable与hashcode()和equals()的密切关系

Hashset是继承Set接口,Set接口又实现Collection接口,这是层次关系。那么Hashset、Hashmap、Hashtable中的存储操作是根据什么原理来存取对象的呢?

        下面以HashSet为例进行分析,我们都知道:在hashset中不允许出现重复对象,元素的位置也是不确定的。在hashset中又是怎样判定元素是否重复的呢?在java的集合中,判断两个对象是否相等的规则是:
         1.判断两个对象的hashCode是否相等

             如果不相等,认为两个对象也不相等,完毕
             如果相等,转入2
           (这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其做为必需的。)

         2.判断两个对象用equals运算是否相等
            如果不相等,认为两个对象也不相等
            如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)
            为什么是两条准则,难道用第一条不行吗?不行,因为前面已经说了,hashcode()相等时,equals()方法也可能不等,所以必须用第2条准则进行限制,才能保证加入的为非重复元素。

例1:

复制代码

 1 package com.bijian.study;2 3 import java.util.HashSet;4 import java.util.Iterator;5 import java.util.Set;6 7 public class HashSetTest {8 9     public static void main(String args[]) {
10         String s1 = new String("aaa");
11         String s2 = new String("aaa");
12         System.out.println(s1 == s2);
13         System.out.println(s1.equals(s2));
14         System.out.println(s1.hashCode());
15         System.out.println(s2.hashCode());
16         Set hashset = new HashSet();
17         hashset.add(s1);
18         hashset.add(s2);
19         Iterator it = hashset.iterator();
20         while (it.hasNext()) {
21             System.out.println(it.next());
22         }
23     }
24 }

复制代码

运行结果:

false
true
96321
96321
aaa

  这是因为String类已经重写了equals()方法和hashcode()方法,所以hashset认为它们是相等的对象,进行了重复添加。

例2:

复制代码

 1 package com.bijian.study;2 3 import java.util.HashSet;4 import java.util.Iterator;5 6 public class HashSetTest {7 8     public static void main(String[] args) {9         HashSet hs = new HashSet();
10         hs.add(new Student(1, "zhangsan"));
11         hs.add(new Student(2, "lisi"));
12         hs.add(new Student(3, "wangwu"));
13         hs.add(new Student(1, "zhangsan"));
14 
15         Iterator it = hs.iterator();
16         while (it.hasNext()) {
17             System.out.println(it.next());
18         }
19     }
20 }
21 
22 class Student {
23     int num;
24     String name;
25 
26     Student(int num, String name) {
27         this.num = num;
28         this.name = name;
29     }
30 
31     public String toString() {
32         return num + ":" + name;
33     }
34 }

复制代码

运行结果:

1:zhangsan  
3:wangwu  
2:lisi  
1:zhangsan 

为什么hashset添加了相等的元素呢,这是不是和hashset的原则违背了呢?回答是:没有。因为在根据hashcode()对两次建立的new Student(1,“zhangsan”)对象进行比较时,生成的是不同的哈希码值,所以hashset把他当作不同的对象对待了,当然此时的equals()方法返回的值也不等。

        为什么会生成不同的哈希码值呢?上面我们在比较s1和s2的时候不是生成了同样的哈希码吗?原因就在于我们自己写的Student类并没有重新自己的hashcode()和equals()方法,所以在比较时,是继承的object类中的hashcode()方法,而object类中的hashcode()方法是一个本地方法,比较的是对象的地址(引用地址),使用new方法创建对象,两次生成的当然是不同的对象了,造成的结果就是两个对象的hashcode()返回的值不一样,所以Hashset会把它们当作不同的对象对待。

        怎么解决这个问题呢?答案是:在Student类中重新hashcode()和equals()方法。

复制代码

class Student {int num;String name;Student(int num, String name) {this.num = num;this.name = name;}public int hashCode() {return num * name.hashCode();}public boolean equals(Object o) {Student s = (Student) o;return num == s.num && name.equals(s.name);}public String toString() {return num + ":" + name;}
}

复制代码

运行结果:

1:zhangsan  
3:wangwu  
2:lisi  

可以看到重复元素的问题已经消除,根据重写的方法,即便两次调用了new Student(1,"zhangsan"),我们在获得对象的哈希码时,根据重写的方法hashcode(),获得的哈希码肯定是一样的,当然根据equals()方法我们也可判断是相同的,所以在向hashset集合中添加时把它们当作重复元素看待了。

重写equals()和hashcode()小结:

  1.重点是equals,重写hashCode只是技术要求(为了提高效率)
      2.为什么要重写equals呢?因为在java的集合框架中,是通过equals来判断两个对象是否相等的
      3.在hibernate中,经常使用set集合来保存相关对象,而set集合是不允许重复的。在向HashSet集合中添加元素时,其实只要重写equals()这一条也可以。但当hashset中元素比较多时,或者是重写的equals()方法比较复杂时,我们只用equals()方法进行比较判断,效率也会非常低,所以引入了hashCode()这个方法,只是为了提高效率,且这是非常有必要的。比如可以这样写:

public int hashCode(){  return 1; //等价于hashcode无效  
}  

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

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

相关文章

嵌入式培训机构四个月实训课程笔记(完整版)-Linux系统编程第五天-Linux消息共享内存练习题(物联技术666)

更多配套资料CSDN地址:点赞+关注,功德无量。更多配套资料,欢迎私信。 物联技术666_嵌入式C语言开发,嵌入式硬件,嵌入式培训笔记-CSDN博客物联技术666擅长嵌入式C语言开发,嵌入式硬件,嵌入式培训笔记,等方面的知识,物联技术666关注机器学习,arm开发,物联网,嵌入式硬件,单片机…

2024龙年艺术字矢量Ai设计文件60套

2024新年将至&#xff0c;设计师们早已开始为龙年海报、推文的制作摩拳擦掌。该合集不仅内容丰富多样,作为矢量文件资源&#xff0c;也能够让设计者更为轻松地编辑与创作。 合集内另附200多张电脑壁纸。 文件总大小368MB 链接&#xff1a;https://pan.quark.cn/s/0caab4cf065…

电商API-获取拼多多商品详情数据精准价格API测试示例

pinduoduo.item_get_app_pro获取拼多多商品详情数据 如何获取apikey&#xff1f; 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff…

【Leetcode】移除后集合的最多元素数

目录 &#x1f4a1;题目描述 &#x1f4a1;思路 &#x1f4a1;总结 100150. 移除后集合的最多元素数 &#x1f4a1;题目描述 给你两个下标从 0 开始的整数数组 nums1 和 nums2 &#xff0c;它们的长度都是偶数 n 。 你必须从 nums1 中移除 n / 2 个元素&#xff0c;同时从 …

Centos 配置 DNS 服务器

在连网的情况下&#xff0c;安装 DNS 服务器&#xff0c;使用命令&#xff1a; yum install bind 安装完成过后&#xff0c;关闭 DNS 服务器防火墙命令&#xff1a; systemctl stop firewalld 在 “ # ” 输入命令&#xff1a; gedit /etc/named.conf 进入过后将 listen…

Docker给容器添加新端口映射

Docker容器添加新端口映射 查看运行的容器 docker ps查看容器挂载目录 docker inspect [容器id]我这里是[容器name] 停止容器 docker stop pythonWarning: Stopping docker.service, but it can still be activated by: docker.socket 停止docker服务 systemctl stop dock…

网络安全—SSL安全访问应用

文章目录 网络拓扑部署CA服务器颁发证书开启Web服务安装IIS服务修改Web默认网页 申请Web证书前提准备申请文件生成申请web证书开始安装web证书 客户机访问web默认网站使用HTTP使用HTTPS 为客户机安装浏览器证书 环境&#xff1a;Windows Server 2003 网络拓扑 这里使用NAT还是…

x64dbg的基本使用

目录 x64dbg简介 术语 打开程序 x64dbg打开界面介绍 汇编窗口 内存窗口 寄存器窗口 堆栈窗口 基本调试方法 搜索字符串 退出程序 x64dbg简介 x64dbg是软件逆向里必不可少的动态调试工具&#xff0c;本来考虑学习一下OD&#xff0c;但是考虑到OD很久之前就已经停止维…

ArcGIS制图技巧总结

Part 1 制图综述 1.1 制图的目的 随着GIS在各行各业的深入应用&#xff0c;各信息化部门和生产单位都逐渐建立起自己的GIS的应用&#xff0c;同时积累了大量的地理数据。随着应用深度和广度的推进&#xff0c;针对数据建立专题应用越来越迫切&#xff0c;对行业专题制图的需…

2024拜年祝福视频AE模板31套

做短视频必备的AE模板非常好看&#xff0c;跨年做个视频非常漂亮&#xff0c;喜欢的赶紧保存吧&#xff01; 链接&#xff1a;https://pan.quark.cn/s/fc1f3db12049

XML技术分析03

一、XMLHttpRequest 对象 XMLHttpRequest 对象用于在后台与服务器交换数据。 创建 XMLHttpRequest 对象 所有现代浏览器 (IE7、Firefox、Chrome、Safari 以及 Opera) 都内建了 XMLHttpRequest 对象。 通过一行简单的 JavaScript 代码&#xff0c;我们就可以创建 XMLHttpReq…

09.简单工厂模式与工厂方法模式

道生一&#xff0c;一生二&#xff0c;二生三&#xff0c;三生万物。——《道德经》 最近小米新车亮相的消息可以说引起了不小的轰动&#xff0c;我们在感慨SU7充满土豪气息的保时捷设计的同时&#xff0c;也深深的被本土品牌的野心和干劲所鼓舞。 今天我们就接着这个背景&…

uniapp 解决安卓App使用uni.requestPayment实现沙箱环境支付宝支付报错

背景&#xff1a;uniapp与Java实现的安卓端app支付宝支付&#xff0c;本想先在沙箱测试环境测支付&#xff0c;但一直提示“商家订单参数异常&#xff0c;请重新发起付款。”&#xff0c;接着报错信息就是&#xff1a;{ "errMsg": "requestPayment:fail [pa…

【书生大模型00--开源体系介绍】

书生大模型开源体系介绍 0 通用人工智能1 InternLM性能及模型2 从模型到应用 大模型成为目前很热的关键词&#xff0c;最热门的研究方向&#xff0c;热门的应用&#xff1b;ChatGPT的横空出世所引爆&#xff0c;快速被人们上手应用到各领域&#xff1b; 0 通用人工智能 相信使…

[蓝桥杯学习] 线段树

学习blibli 定义 线段树是一种特殊的平衡二叉查找树&#xff0c;使用线段树&#xff0c;可以实现数据的添加、查找和删除。 树的根结点表示了一个完整的单元区间&#xff0c;左右孩子的区间是将父结点的区间进行二分&#xff0c;左右孩子的区间之和&#xff0c;就是他们的根…

【机器学习】循环神经网络(四)-应用

五、应用-语音识别 5.1 语音识别问题 详述语音识别的经典方法GMMHMM框架 5.2 深度模型 详述DNN-HMM结构 循环神经网络与CTC技术结构用于语音识别问题 六、自然语言处理 RNN-LM建模方法 6.1 中文分词 6.2 词性标注 6.3 命名实体识别 详述LSTMCRF进行命名实体识别的方法 6.4 文本…

ElasticSearch 性能优化

提升写入性能 使用 bulk 接口批量写入 节省重复创建连接的网络开销通过进行基准测试来找到最佳的批处理数量 延长 refresh 的时间间隔 通过延长 refresh&#xff08;刷新&#xff09;的时间间隔可以降低段合并的频率&#xff0c;段合并十分耗费资源默认的刷新频率为1s&…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《基于两阶段随机优化的电能量与深度调峰融合市场出清模型及定价方法》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主的专栏栏目《论文与完整程序》 这个标题涉及到一个电力市场的建模和定价方法&#xff0c;采用了两阶段随机优化的策略&#xff0c;目标是实现电能量与深度调峰的融合。下面是对标题中各个…

百度搜索金融:构建高时效、高可用的分布式数据传输系统

作者 | 搜索技术平台研发部 导读 分布式数据传输系统是一种用于在多个计算节点之间高效传输大量数据的系统&#xff0c;诣在高效的解决大规模数据迁移、备份、跨地域复制等问题&#xff0c;其广泛应用在实时数据流传输、跨数据中心数据迁移、多媒体传输等场景&#xff0c;在大多…

智能合约:3分钟开发ERC20 token(2)

0.前言 上一节我们讲到了开发智能合约的准备工作&#xff0c;以及在线编程平台remix 智能合约&#xff08;1&#xff09; 这一节讲解如何开发、发行一个代币&#xff0c;并具备包括代币铸造mint&#xff0c;转账transfer和销毁burn功能&#xff0c;并确保合约拥有者owner的权限…