HashCode和equals的理解
-------------------------------------------------------------------------------------------第一篇博客-------------------------------------------------------------------------------------------------
前言
在程序设计中,有很多的“公约”,遵守约定去实现你的代码,会让你避开很多坑,这些公约是前人总结出来的设计规范。
Object类是Java中的万类之祖,其中,equals和hashCode是2个非常重要的方法。
这2个方法总是被人放在一起讨论。最近在看集合框架,为了打基础,就决定把一些细枝末节清理掉。一次性搞清楚!
下面开始剖析。
public boolean equals(Object obj)
Object类中默认的实现方式是 : return this == obj 。那就是说,只有this 和 obj引用同一个对象,才会返回true。
而我们往往需要用equals来判断 2个对象是否等价,而非验证他们的唯一性。这样我们在实现自己的类时,就要重写equals.
按照约定,equals要满足以下规则。
自反性: x.equals(x) 一定是true
对null: x.equals(null) 一定是false
对称性: x.equals(y) 和 y.equals(x)结果一致
传递性: a 和 b equals , b 和 c equals,那么 a 和 c也一定equals。
一致性: 在某个运行时期间,2个对象的状态的改变不会不影响equals的决策结果,那么,在这个运行时期间,无论调用多少次equals,都返回相同的结果。
一个例子

1 class Test
2 {
3 private int num;
4 private String data;
5
6 public boolean equals(Object obj)
7 {
8 if (this == obj)
9 return true;
10
11 if ((obj == null) || (obj.getClass() != this.getClass()))
12 return false;
13
//能执行到这里,说明obj和this同类且非null。
14 Test test = (Test) obj;
15 return num == test.num&& (data == test.data || (data != null && data.equals(test.data)));
16 }
17
18 public int hashCode()
19 {
20 //重写equals,也必须重写hashCode。具体后面介绍。
24 }
25
26 }

equals编写指导
Test类对象有2个字段,num和data,这2个字段代表了对象的状态,他们也用在equals方法中作为评判的依据。
在第8行,传入的比较对象的引用和this做比较,这样做是为了 save time ,节约执行时间,如果this 和 obj是 对同一个堆对象的引用,那么,他们一定是qeuals 的。
接着,判断obj是不是为null,如果为null,一定不equals,因为既然当前对象this能调用equals方法,那么它一定不是null,非null 和 null当然不等价。
然后,比较2个对象的运行时类,是否为同一个类。不是同一个类,则不equals。getClass返回的是 this 和obj的运行时类的引用。如果他们属于同一个类,则返回的是同一个运行时类的引用。注意,一个类也是一个对象。
1、有些程序员使用下面的第二种写法替代第一种比较运行时类的写法。应该避免这样做。

if((obj == null) || (obj.getClass() != this.getClass())) return false; if(!(obj instanceof Test)) return false; // avoid 避免!

它违反了公约中的对称原则。
例如:假设Dog扩展了Aminal类。
dog instanceof Animal 得到true
animal instanceof Dog 得到false
这就会导致
animal.equls(dog) 返回true
dog.equals(animal) 返回false
仅当Test类没有子类的时候,这样做才能保证是正确的。
2、按照第一种方法实现,那么equals只能比较同一个类的对象,不同类对象永远是false。但这并不是强制要求的。一般我们也很少需要在不同的类之间使用equals。
3、在具体比较对象的字段的时候,对于基本值类型的字段,直接用 == 来比较(注意浮点数的比较,这是一个坑)对于引用类型的字段,你可以调用他们的equals,当然,你也需要处理字段为null 的情况。对于浮点数的比较,我在看Arrays.binarySearch的源代码时,发现了如下对于浮点数的比较的技巧:
if ( Double.doubleToLongBits(d1) == Double.doubleToLongBits(d2) ) //d1 和 d2 是double类型 if( Float.floatToIntBits(f1) == Float.floatToIntBits(f2) ) //f1 和 f2 是d2是float类型
4、并不总是要将对象的所有字段来作为equals 的评判依据,那取决于你的业务要求。比如你要做一个家电功率统计系统,如果2个家电的功率一样,那就有足够的依据认为这2个家电对象等价了,至少在你这个业务逻辑背景下是等价的,并不关心他们的价钱啊,品牌啊,大小等其他参数。
5、最后需要注意的是,equals 方法的参数类型是Object,不要写错!
public int hashCode()
这个方法返回对象的散列码,返回值是int类型的散列码。
对象的散列码是为了更好的支持基于哈希机制的Java集合类,例如 Hashtable, HashMap, HashSet 等。
关于hashCode方法,一致的约定是:
重写了euqls方法的对象必须同时重写hashCode()方法。
如果2个对象通过equals调用后返回是true,那么这个2个对象的hashCode方法也必须返回同样的int型散列码
如果2个对象通过equals返回false,他们的hashCode返回的值允许相同。(然而,程序员必须意识到,hashCode返回独一无二的散列码,会让存储这个对象的hashtables更好地工作。)
在上面的例子中,Test类对象有2个字段,num和data,这2个字段代表了对象的状态,他们也用在equals方法中作为评判的依据。那么, 在hashCode方法中,这2个字段也要参与hash值的运算,作为hash运算的中间参数。这点很关键,这是为了遵守:2个对象equals,那么 hashCode一定相同规则。
也是说,参与equals函数的字段,也必须都参与hashCode 的计算。
合乎情理的是:同一个类中的不同对象返回不同的散列码。典型的方式就是根据对象的地址来转换为此对象的散列码,但是这种方式对于Java来说并不是唯一的要求的
的实现方式。通常也不是最好的实现方式。
相比 于 equals公认实现约定,hashCode的公约要求是很容易理解的。有2个重点是hashCode方法必须遵守的。约定的第3点,其实就是第2点的
细化,下面我们就来看看对hashCode方法的一致约定要求。
第一:在某个运行时期间,只要对象的(字段的)变化不会影响equals方法的决策结果,那么,在这个期间,无论调用多少次hashCode,都必须返回同一个散列码。
第二:通过equals调用返回true 的2个对象的hashCode一定一样。
第三:通过equasl返回false 的2个对象的散列码不需要不同,也就是他们的hashCode方法的返回值允许出现相同的情况。
总结一句话:等价的(调用equals返回true)对象必须产生相同的散列码。不等价的对象,不要求产生的散列码不相同。
hashCode编写指导
在编写hashCode时,你需要考虑的是,最终的hash是个int值,而不能溢出。不同的对象的hash码应该尽量不同,避免hash冲突。
那么如果做到呢?下面是解决方案。
1、定义一个int类型的变量 hash,初始化为 7。
接下来让你认为重要的字段(equals中衡量相等的字段)参入散列运,算每一个重要字段都会产生一个hash分量,为最终的hash值做出贡献(影响)
重要字段var的类型 | 他生成的hash分量 |
---|---|
byte, char, short , int | (int)var |
long | (int)(var ^ (var >>> 32)) |
boolean | var?1:0 |
float | Float.floatToIntBits(var) |
double | long bits = Double.doubleToLongBits(var); 分量 = (int)(bits ^ (bits >>> 32)); |
引用类型 | (null == var ? 0 : var.hashCode()) |
最后把所有的分量都总和起来,注意并不是简单的相加。选择一个倍乘的数字31,参与计算。然后不断地递归计算,直到所有的字段都参与了。
int hash = 7; hash = 31 * hash + 字段1贡献分量; hash = 31 * hash + 字段2贡献分量; ..... return hash;
------------------------------------------------------------------------------------------------------------第二篇博客--------------------------------------------------------------------------------------------
1、hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;
2、如果两个对象相同,就是适用于equals(Java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;
3、如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;
4、两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。
再归纳一下就是hashCode是用于查找使用的,而equals是用于比较两个对象的是否相等的。以下这段话是从别人帖子回复拷贝过来的:
- 1.hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有
- 例如内存中有这样的位置
- 0 1 2 3 4 5 6 7
- 而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。
- 但如果用hashcode那就会使效率提高很多。
- 我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,9除8的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除 8求余数直接找到存放的位置了。
- 2.但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),例如9除以8和17除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。
- 也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。
- 那么。重写了equals(),为什么还要重写hashCode()呢?
- 想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊
最后,我们来看一个具体的示例吧,
- public class HashTest {
- private int i;
- public int getI() {
- return i;
- }
- public void setI(int i) {
- this.i = i;
- }
- public int hashCode() {
- return i % 10;
- }
- public final static void main(String[] args) {
- HashTest a = new HashTest();
- HashTest b = new HashTest();
- a.setI(1);
- b.setI(1);
- Set<HashTest> set = new HashSet<HashTest>();
- set.add(a);
- set.add(b);
- System.out.println(a.hashCode() == b.hashCode());
- System.out.println(a.equals(b));
- System.out.println(set);
- }
- }
这个输出的结果:
- true
- false
- [com.ubs.sae.test.HashTest@1, com.ubs.sae.test.HashTest@1]
以上这个示例,我们只是重写了hashCode方法,从上面的结果可以看出,虽然两个对象的hashCode相等,但是实际上两个对象并不是相等;,我们没有重写equals方法,那么就会调用object默认的equals方法,是比较两个对象的引用是不是相同,显示这是两个不同的对象,两个对象的引用肯定是不定的。这里我们将生成的对象放到了HashSet中,而HashSet中只能够存放唯一的对象,也就是相同的(适用于equals方法)的对象只会存放一个,但是这里实际上是两个对象a,b都被放到了HashSet中,这样HashSet就失去了他本身的意义了。
此时我们把equals方法给加上:
- public class HashTest {
- private int i;
- public int getI() {
- return i;
- }
- public void setI(int i) {
- this.i = i;
- }
- <span style="color:#3366FF;"><strong>public boolean equals(Object object) {
- if (object == null) {
- return false;
- }
- if (object == this) {
- return true;
- }
- if (!(object instanceof HashTest)) {
- return false;
- }
- HashTest other = (HashTest) object;
- if (other.getI() == this.getI()) {
- return true;
- }
- return false;
- }</strong></span>
- public int hashCode() {
- return i % 10;
- }
- public final static void main(String[] args) {
- HashTest a = new HashTest();
- HashTest b = new HashTest();
- a.setI(1);
- b.setI(1);
- Set<HashTest> set = new HashSet<HashTest>();
- set.add(a);
- set.add(b);
- System.out.println(a.hashCode() == b.hashCode());
- System.out.println(a.equals(b));
- System.out.println(set);
- }
- }
此时得到的结果就会如下:
- true
- true
- [com.ubs.sae.test.HashTest@1]
从结果我们可以看出,现在两个对象就完全相等了,HashSet中也只存放了一份对象。
HashCode和equals的理解的更多相关文章
- 对hashcode、equals的理解
1.首先hashcode和equals都是java每个对象都存在的方法,因为他们两是Object的方法. 2.hashcode方法默认返回的是该对象内存地址的哈希码,然而你会发现,Object类中没有 ...
- HashCode()与equals()深入理解
1.hashCode()和equals()方法都是Object类提供的方法, hashCode()返回该对象的哈希码值,该值通常是一个由该对象的内部地址转换而来的int型整数, Object的equa ...
- 对hashmap与hashcode()、equals()的理解
1.equals方法没被重写的时候 比较的只是对象的地址 重写之后 比较的才是对象里的内容 2.重写equals的时候 务必需要重写hashcode 不然在用到容器的时候 会出现问题 因为容器会 ...
- java中hashcode()和equals()的详解
今天下午研究了半天hashcode()和equals()方法,终于有了一点点的明白,写下来与大家分享(zhaoxudong 2008.10.23晚21.36). 1. 首先equals()和hashc ...
- Java hashCode() 和 equals()的若干问题
原文:http://www.cnblogs.com/skywang12345/p/3324958.html 本章的内容主要解决下面几个问题: 1 equals() 的作用是什么? 2 equals() ...
- Java hashCode() 和 equals()的若干问题解答
本章的内容主要解决下面几个问题: 1 equals() 的作用是什么? 2 equals() 与 == 的区别是什么? 3 hashCode() 的作用是什么? 4 hashCode() 和 equa ...
- Java中hashcode,equals和==
hashcode方法返回该对象的哈希码值. hashCode()方法可以用来来提高Map里面的搜索效率的,Map会根据不同的hashCode()来放在不同的位置,Map在搜索一个对象的时候先通过has ...
- java中hashcode和equals的区别和联系
HashSet和HashMap一直都是JDK中最常用的两个类,HashSet要求不能存储相同的对象,HashMap要求不能存储相同的键. 那么Java运行时环境是如何判断HashSet中相同对象.Ha ...
- Java 中正确使用 hashCode 和 equals 方法
在这篇文章中,我将告诉大家我对hashCode和equals方法的理解.我将讨论他们的默认实现,以及如何正确的重写他们.我也将使用Apache Commons提供的工具包做一个实现. 目录: hash ...
随机推荐
- React组件的使用
一.index.js 文件[基本配置] //react语法塘 import React from 'react'; //reactDom用来操作虚拟DOM import ReactDom from ...
- [转]关于sdk更新Android SDK Tools 25.3.1版本后使用sdk manager闪退
昨天这两个manager还工作正常,今天更新了一下,发现不可用了,运行avd manager和sdk manager没反应,搜了好多文章,然后看到了下这篇文章<关于sdk更新Android SD ...
- django使用pycharm为项目选择虚拟环境-3.1
使用pycharm打开项目 选择右上角的 file - settings - project - project interpreter 选择左上的设置符号,选择 Add 选择红框部分,然后选择之前创 ...
- django_ORM学生管理系统
一.新建django项目准备工作 CMD新建项目命令:django-admin startproject [项目名称] pycharm的project目录里新建app命令:python manage. ...
- 中国大学MOOC-C程序设计(浙大翁恺)—— 素数和
题目内容: 我们认为2是第一个素数,3是第二个素数,5是第三个素数,依次类推. 现在,给定两个整数n和m,0<n<=m<=200,你的程序要计算第n个素数到第m个素数之间所有的素数的 ...
- golang for循环里面创建协程问题的研究
原本想在一个for里面创建10个协程,这些协程顺序拿到for的递增变量,把这10个递增变量都打印出来.但事与愿违,于是做实验,查书,思考,写出以下记录. golang里,在for循环里面起协程,如下代 ...
- 成都Uber优步司机奖励政策(3月23日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- LeetCode: 63. Unique Paths II(Medium)
1. 原题链接 https://leetcode.com/problems/unique-paths-ii/description/
- beego orm mysql
beego框架中的rom支持mysql 项目中使用到mvc模式,总结下使用方式: models中 package models import ( //使用beego orm 必备 "gith ...
- PS 证件照换颜色
1.打开要修改的图片,然后先Ctrl+J备份一份 2.点击魔法棒,点击要换颜色的地方,如衣服,之后会出现虚线,如果自动选择的不全,可以按住Shift键自行选择区域 3.然后Shift+Fn+F5(由于 ...