博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
hashcode和equals方法详细解析, hashmap对于hashcode方法的使用
阅读量:5171 次
发布时间:2019-06-13

本文共 6807 字,大约阅读时间需要 22 分钟。

一 第一篇 http://jameswxx.iteye.com/blog/647451  字符串引出来

         前几天有个同事问我,String a="123",String b=new String("123");它们的hashcode相等吗?我当时愣了一下,首先它们的equals肯定是true的,“==”是false的,但是还真没注意到两个的hashcode是否相等。

       (下面插入代码测试正确)

      

package equals.hashcode;public class TestHashcode {	/**	 * @param args	 */	public static void main(String[] args) {		// TODO Auto-generated method stub		String a = "what";		String b = new String("what");				System.out.println(a == b);		System.out.println(a.equals(b));                System.out.println(a.hashCode() == b.hashCode());        }}
输出结果是 false  true true。说明hashcode一样

 

       后来我查了一下jdk文档,发现对String的hashcode是这样描述的:

hashCode

public int hashCode()
返回此字符串的哈希码。String 对象的哈希码按下列公式计算:
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
使用 int 算法,这里 s[i] 是字符串的第i 个字符,n 是字符串的长度,^ 表示求幂。(空字符串的哈希码为 0。)

 

覆盖:
中的
返回:
此对象的哈希码值。
另请参见:
,

这说明,String的hashcode其实是对它的字符串的一系列运算,所以只要两个String对象的字符串值是相等的,那么他们的hashcode也是一定相等的。String类被定义为final类型,final类使不能被继承的,因为它的方法不可能被重写。

 

 

按找文档描述,String的hashcode方法重写了Object的hashcode方法,那么我们再来看看Object的hashcode方法是什么样的:

hashCode

public int hashCode()
返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。

hashCode 的常规协定是:

  • 在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
  • 如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用hashCode 方法都必须生成相同的整数结果。
  • 以下情况 是必需的:如果根据 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。

实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)

 

返回:
此对象的一个哈希码值。
另请参见:
,

 

Object定义的hashcode方法就是把对象内存地址转换为一个数值,这意味着两个Object必须完全相等(==而不是equals),hashcode才是相等。但是我们再看下,是不是两个对象的hashcode相同,那么他们的equals比较就是true呢?是不是他们的完全比较“==”就是true呢?答案是不一定。这要看这两个对象有没有重写Object的hashCode方法和equals方法。如果没有重写,是按Object默认的方式去处理,Object的equals方法定义如下:

 

 

equals

public boolean equals( obj)
指示某个其他对象是否与此对象“相等”。

equals 方法在非空对象引用上实现相等关系:

  • 自反性:对于任何非空引用值xx.equals(x) 都应返回 true
  • 对称性:对于任何非空引用值xy,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回true
  • 传递性:对于任何非空引用值xyz,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回true
  • 一致性:对于任何非空引用值xy,多次调用 x.equals(y) 始终返回 true 或始终返回false,前提是对象上 equals 比较中所用的信息没有被修改。
  • 对于任何非空引用值 xx.equals(null) 都应返回false

Object 类的equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值 xy,当且仅当xy 引用同一个对象时,此方法才返回 truex == y 具有值true)。

注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

 

参数:
obj - 要与之比较的引用对象。
返回:
如果此对象与 obj 参数相同,则返回 true;否则返回false
另请参见:
,

 

(注意这句话“Object 类的 equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值xy,当且仅当 xy 引用同一个对象时,此方法才返回truex == y 具有值 true) ”。这说明了Obejct定义的equals其实和“==”没什么两样。所以如果对象没有重写Object的hashCode方法和equals方法,那么可以肯定的说,如果两个对象的hashcode相等,那么equals比较必然是true,“==”比较也必然是true。但是如果对象重写了hashCode方法和equals方法,那么情况就不一样了,只能说有可能,但是不能肯定。比如String重写了Object的hashcode和equals,但是两个String如果hashcode相等,那么equals比较肯定是相等的,但是“==”比较却不一定相等。如果自定义的对象重写了hashCode方法,有可能hashcode相等,equals却不一定相等,“==”比较也不一定相等。)

HashMap是通过链地址法解决hash collision

主要原因是默认从Object继承来的hashCode是基于对象的ID实现的。

如果你重载了equals,比如说是基于对象的内容实现的,而保留hashCode的实现不变,那么很可能某两个对象明明是“相等”,而hashCode却不一样。
这样,当你用其中的一个作为键保存到hashMap、hasoTable或hashSet中,再以“相等的”找另一个作为键值去查找他们的时候,则根本找不到。

Map.put(key,value)时根据key.hashCode生成一个内部hash值,根据这个hash值将对象存放在一个table

Map.get(key)会比较key.hashCodeequals方法,当且仅当这两者相等时,才能正确定位到table;(所以,如果重写了equals方法,使逻辑上相等,但是如果没有重写hashcode方法,在map里面,两个equals的值无法根据其中一个查到另外一个了,因为hashcode不相同。所以,equals的对象一定要hashcode也相同,反过来则不然)

因为java中默认的hashCode是根据对象的地址计算得到的,虽然p1.equals(p2)=true,但是p1,p1有不同的内存地址,所以有不同的hashCode;所以通过p2是不能得到value的,这个时候value==null;添加hashCode()后,两个对象有相同hashCode,所以能得到

javaSet是通过Map实现的,所以MapSet的所有实现类都要注意这一点

HashMap是通过链地址法解决hash collision的,并且新对象都是添加到表头的(这个看了好久才明白,数据结构都还老师了)

三 总结自Effective Java 的知识点

   

以下内容总结自《Effective Java》。

1.何时需要重写equals()

当一个类有自己特有的“逻辑相等”概念(不同于对象身份的概念)。

2.设计equals()

[1]使用instanceof操作符检查“实参是否为正确的类型”。

[2]对于类中的每一个“关键域”,检查实参中的域与当前对象中对应的域值。

[2.1]对于非float和double类型的原语类型域,使用==比较;

[2.2]对于对象引用域,递归调用equals方法;

[2.3]对于float域,使用Float.floatToIntBits(afloat)转换为int,再使用==比较;

[2.4]对于double域,使用Double.doubleToLongBits(adouble) 转换为int,再使用==比较;

[2.5]对于数组域,调用Arrays.equals方法。

3.当改写equals()的时候,总是要改写hashCode()

根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode方法,它们仅仅是两个对象。因此,违反了“相等的对象必须具有相等的散列码”。

4.设计hashCode()

[1]把某个非零常数值,例如17,保存在int变量result中;

[2]对于对象中每一个关键域f(指equals方法中考虑的每一个域):

[2.1]boolean型,计算(f ? 0 : 1);

[2.2]byte,char,short型,计算(int);

[2.3]long型,计算(int) (f ^(f>>>32));

[2.4]float型,计算Float.floatToIntBits(afloat);

[2.5]double型,计算Double.doubleToLongBits(adouble)得到一个long,再执行[2.3];

[2.6]对象引用,递归调用它的hashCode方法;

[2.7]数组域,对其中每个元素调用它的hashCode方法。

[3]将上面计算得到的散列码保存到int变量c,然后执行 result=37*result+c;

[4]返回result。

5.示例

下面的这个类遵循上面的设计原则,重写了类的equals()hashCode()

package com.zj.unit;import java.util.Arrays; public class Unit {    private short ashort;    private char achar;    private byte abyte;    private boolean abool;    private long along;    private float afloat;    private double adouble;    private Unit aObject;    private int[] ints;    private Unit[] units;     public boolean equals(Object o) {       if (!(o instanceof Unit))           return false;       Unitunit = (Unit) o;       return unit.ashort == ashort              &&unit.achar == achar              &&unit.abyte == abyte              &&unit.abool == abool              &&unit.along == along              &&Float.floatToIntBits(unit.afloat) ==Float                     .floatToIntBits(afloat)              &&Double.doubleToLongBits(unit.adouble) ==Double                     .doubleToLongBits(adouble)              && unit.aObject.equals(aObject)&&equalsInts(unit.ints)              &&equalsUnits(unit.units);    }     private boolean equalsInts(int[] aints) {       return Arrays.equals(ints,aints);    }     private boolean equalsUnits(Unit[] aUnits) {       return Arrays.equals(units,aUnits);    }     public int hashCode() {       int result = 17;       result= 37 * result + (int) ashort;       result= 37 * result + (int) achar;       result= 37 * result + (int) abyte;       result= 37 * result + (abool ? 0 : 1);       result= 37 * result + (int) (along ^ (along >>> 32));       result= 37 * result + Float.floatToIntBits(afloat);       long tolong = Double.doubleToLongBits(adouble);       result= 37 * result + (int) (tolong ^ (tolong>>> 32));       result= 37 * result + aObject.hashCode();       result= 37 * result + intsHashCode(ints);       result= 37 * result + unitsHashCode(units);       return result;    }     private int intsHashCode(int[] aints) {       int result = 17;       for (int i = 0; i < aints.length;i++)           result= 37 * result + aints[i];       return result;    }     private int unitsHashCode(Unit[] aUnits) {       int result = 17;       for (int i = 0; i < aUnits.length;i++)           result= 37 * result + aUnits[i].hashCode();       return result;    }}

 

转载于:https://www.cnblogs.com/allenzhaox/archive/2012/08/13/3201830.html

你可能感兴趣的文章
静态代码审查工具FxCop插件开发(c#)
查看>>
创建代码仓库
查看>>
理解裸机部署过程ironic
查看>>
Django 组件-ModelForm
查看>>
zabbix 二 zabbix agent 客户端
查看>>
大数据分析中,有哪些常见的大数据分析模型?
查看>>
如何防止Arp攻击
查看>>
ClassList 标签的用法
查看>>
小细节:Java中split()中的特殊分隔符 小数点
查看>>
【编程思想】【设计模式】【行为模式Behavioral】中介者模式Mediator
查看>>
后端接口时间戳或者随机数的作用
查看>>
tomcat docBase 和 path
查看>>
java默认语法、EL、JSTL表达式,JSTL和struts Tag标签的使用总结
查看>>
Vue笔记:使用 axios 发送请求
查看>>
富文本编辑器 - RichEditor
查看>>
java webcontroller访问时报415错误
查看>>
qcow2、raw、vmdk等镜像格式
查看>>
Jzoj5455【NOIP2017提高A组冲刺11.6】拆网线
查看>>
特定字符序列的判断(1028)
查看>>
华为面试
查看>>