Java中hashcode方法与equals方法关联关系的直观解释

来源:互联网 发布:优酷总是网络连接失败 编辑:程序博客网 时间:2024/06/06 03:07

我的问题列表
1、“重写equals方法一定要同时重写hashcode方法”。那么是否hashcode方法用到equals方法的返回值呢,否则为啥有这样的规定呢
2、“两个对象equals()返回true的时候,那么hashcode一定需要一致;而hashcode一致的时候,equals不一定返回true”。这句话又应该怎么理解呢。两个方法直接的关系变的有些复杂了。

总结:很多解答hashcode和equals方法的地方都不断的提到2里面的两句话,但是这两个方法直接就没有更直接的联系吗.
写这篇文章的目的就是为了弄清为什么会有上面2提到的两句话,以及hashcode和equals方法的真实关联关系。

要正确理解这个问题需要:散列表是什么,哈希算法,哈希表,Object源码,hashcode的native方法实现

下面是hashcode方法在jdk1.7的api里面的描述。
jdk1.7-hashcode
为这个对象返回一个hashcode,这个方法是为了给哈希表提供支持的
hashcode的通用协议如下:
1、在一个应用程序运行的时候,一个对象调用hashcode方法的时候每次返回的值是一致的,前提是对象内部的信息没有改变 。但是如果这个应用程序重启了,那hashcode可能不会跟之前一次启动时候相同,但是也会在这次启动期间保持不变
2、如果两个对象equals返回的是true,那么这两个对象返回的hashcode必须是一致的,
3、而两个对象equals返回false时,那么这两个对象的hashcode可以是相同(为了好的性能,尽量不同)

而equals方法在Objcet类里的描述是:

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

这里的判断方式是“==”,“==”运算符底层的实现我们是看不到,可以想象一下,它应该是根据两个对象在jvm线程的虚拟地址空间中的地址进行判断的,如果一个类重写equals方法,那么就改变了以前的判断方式。那equals的判断变化了,为什么会影响到hashcode方法呢,原因就是hashcode的“general contract”提到了“两个对象equals,那么hashcode一定要相同”,所以hashcode( )也要改动,那到底为什么会有一个这样的规定呢?更甚者,为什么还允许hashcode相同的情况下,equals返回false呢。

举个例子,身份证号与人是一一对应的,当我们认为两个人相等的时候(equals返回true),他们的身份证号必定是一样的(hashcode相等),但是身份证号相同,我们却不能判断两个人是一个人,这个逻辑就很难理解了。
需要说明的是,这个理解是不对,如果想理解更深,那就要理解一个数据结构——散列表(也叫做哈希表)
举另外一个例子,有n个存放东西的篮子,每个篮子都有一个唯一的编号(hashcode),我们现在要把m个物品放到这些篮子里面,假设m个物品有自己的编号,我们通过一定的规则(哈希函数,或者叫散列函数)放置的时候,如果m>n那么势必出现一个篮子里面多个物品的情况,但是他们放置在一个篮子(hashcode相同)里并不意味着他们本身是相同的(equals返回true),而如果是相同的,那么一定放在同一个篮子里面。
根据上面的例子举一个更加具体的例子,我们有0-99编号(记作n)的鸡蛋,要放入0,1,2编号(记作m)的三个篮子里面,当然我们可以随便放,规则由我们制定,如果我们按照i= m%3的规则放置:
那么0,3,6,9,。。。99这些放在了0号篮子,
1,4,7,。。。,97放在了1号篮子
2,5,8,。。。,98放在了2号篮子
这个i=m%3的规则就是散列函数(哈希),而i就是散列值(哈希值),那么这时候equals()和hashcode()的区别与联系就很明显了,equals比较的是鸡蛋的编号,在鸡蛋和篮子的例子中可以说是互不相等的,而这些鸡蛋的hashcode()的值可以说是很多重复的。

以上的例子很好的解释了hashcode和equals方法的联系,现在回到文章顶部的问题列表,现在可以很好的2这两种说法,更直观的明白了为何会存在这一说法。


接下来还有一个问题,重写equals方法,为何要同时重写hashcode方法呢?
根据上面的例子进行说明,首先我们明确一下之前我们认为鸡蛋是“相同的”这一概念,我们认为鸡蛋的编号相同那么两个鸡蛋是一样的,加入我们拿来一个鸭蛋给它编号10,那我们就认为这个鸭蛋跟之前的10号鸡蛋是一样的,也就是说equals可以说是一种判断规则,它本身并不存在什么“道理”,重新看一次java中Object类的equals方法,

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

“==”这个运算规定了是否相等。它是针对this和obj 这两个引用所指向的内存地址的一种判断。如果我们重写这个方法,那就是要换上我们想要的一种规则,比较常见的就是我们比较对象的内容而不是内存地址。
回到我们的例子,如果我们重新定义了鸡蛋相同的说法,只要鸡蛋重量一样,我们就认为它们相同,这就是相当于重写了equals方法,那么如果不改变hashcode方法,我们会得到什么样的结果呢。在往篮子里面放鸡蛋的时候,我们会得到之前一样的分类,如果0号鸡蛋和 1号鸡蛋有一样的重量,按说我们认为它们相等,但是他们的篮子号(hashcode)却不相同,这个结果也许你觉得没有什么影响,我们之间运行equals方法不就行了吗,反正他们重量相同就返回true。一般情况下是这样的,这也是为什么,很多人错误的没有重写hashcode方法而时常无法察觉到错误的原因。但是如果他使用hashmap和hashtable并且把重写了equals方法的对象当做key值就会出错了。这个在下一篇文章再进行说明。

0 0
原创粉丝点击