一 第一篇 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
方法在非空对象引用上实现相等关系:
- 自反性:对于任何非空引用值
x
,x.equals(x)
都应返回true
。- 对称性:对于任何非空引用值
x
和y
,当且仅当y.equals(x)
返回true
时,x.equals(y)
才应返回true
。- 传递性:对于任何非空引用值
x
、y
和z
,如果x.equals(y)
返回true
,并且y.equals(z)
返回true
,那么x.equals(z)
应返回true
。- 一致性:对于任何非空引用值
x
和y
,多次调用 x.equals(y) 始终返回true
或始终返回false
,前提是对象上equals
比较中所用的信息没有被修改。- 对于任何非空引用值
x
,x.equals(null)
都应返回false
。
Object
类的equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值x
和y
,当且仅当x
和y
引用同一个对象时,此方法才返回true
(x == y
具有值true
)。注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
参数:
obj
- 要与之比较的引用对象。 返回: 如果此对象与 obj 参数相同,则返回true
;否则返回false
。 另请参见: ,
(注意这句话“Object 类的 equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值
x
和y
,当且仅当x
和y
引用同一个对象时,此方法才返回true
(x == 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.hashCode和equals方法,当且仅当这两者相等时,才能正确定位到table;(所以,如果重写了equals方法,使逻辑上相等,但是如果没有重写hashcode方法,在map里面,两个equals的值无法根据其中一个查到另外一个了,因为hashcode不相同。所以,equals的对象一定要hashcode也相同,反过来则不然)
因为java中默认的hashCode是根据对象的地址计算得到的,虽然p1.equals(p2)=true,但是p1,p1有不同的内存地址,所以有不同的hashCode;所以通过p2是不能得到value的,这个时候value==null;添加hashCode()后,两个对象有相同hashCode,所以能得到
java中Set是通过Map实现的,所以Map和Set的所有实现类都要注意这一点
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; }}