hashcode和equals方法详细解析, hashmap对于hashcode方法的使用

一 第一篇 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。)

 


覆盖:Object 中的hashCode
返回:此对象的哈希码值。 另请参见:Object.equals(java.lang.Object),Hashtable

这说明,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 方法都必须生成相同的整数结果。
  • 以下情况 是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。

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

 


返回:此对象的一个哈希码值。 另请参见:equals(java.lang.Object),Hashtable

 

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

 

 

equals

public boolean equals(Object 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另请参见:hashCode(),Hashtable

 

(注意这句话“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/p/3201830.html