hashCode

说明:

本系列博客是本人在工作中遇到的一些问题的整理,其中有些资料来源网络博客,有些信息来自出版的书籍,掺杂一些个人的猜想及验证,总结,主要目的是方便知识的查看,并非纯原创。本系列博客会不断更新。原创不容易,支持原创。对于参考的一些其他博客,会尽量把博客地址列在博客的后面,以方便知识的查看。

 

本篇博客可以看做是《Effective Java中文版2版》第三章(对于所有对象都通用的方法)第九条(覆盖equals时总要覆盖hashCode)的读书笔记,其中掺杂了一些个人的想法及验证。

 

我们来看下Object类中hashCode方法的实现:

 

public native int hashCode();

 

这是一个本地方法

 

java规范对其描述是这样的:

1、在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。

2、如果两个对象根据equals方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生相同的整数结果。

3、如果两个对象根据equals方法比较是不相等的,那么调用这两个对象中国任意一个对象的hashCode方法,不一定产生不同的整数结果。但是给不相等的对象产生不同的整数结果,有可能提高散列表的性能。

 

书中的一个例子:

/**

 * Created with IntelliJ IDEA.

 * User: [email protected]

 * Date: 2014-08-20 13:27

 * Description:

 */

public class PhoneNumber {

    private short areaCode;

    private short prefix;

    private short lineNumber;

    public PhoneNumber(int areaCode,int prefix,int lineNumber){

        this.areaCode = (short)areaCode;

        this.prefix = (short)prefix;

        this.lineNumber = (short)lineNumber;

    }

}

此处没有覆盖hashCodeequals方法,看下测试:

/**

 * Created with IntelliJ IDEA.

 * User: [email protected]

 * Date: 2014-08-19 23:41

 * Description:

 */

public class HashCodeTest {

    public static void main(String[] args) {

        PhoneNumber phoneNumber1 = new PhoneNumber(1,2,3);

        PhoneNumber phoneNumber2 = new PhoneNumber(1,2,3);

        System.out.println("phoneNumber1.hashCode:" + phoneNumber1.hashCode());

        System.out.println("phoneNumber2.hashCode:" + phoneNumber2.hashCode());

    }

}

控制台输出:

phoneNumber1.hashCode:24355087

phoneNumber2.hashCode:5442986

类的不同实例,他们的hashCode不同

 

我们来加上equals方法:

@Override   

public boolean equals(Object obj) {

    if (obj == this){

        return true;

    }

    if (!(obj instanceof PhoneNumber)){

        return false;

    }

    PhoneNumber p = (PhoneNumber)obj;

    return this.areaCode == p.areaCode

                && this.prefix == p.prefix

                && this.lineNumber == p.lineNumber;

}

 

看下如下测试:

public class HashCodeTest {

    public static void main(String[] args) {

        PhoneNumber phoneNumber1 = new PhoneNumber(1,2,3);

        PhoneNumber phoneNumber2 = new PhoneNumber(1,2,3);

        System.out.println("phoneNumber1.hashCode:" + phoneNumber1.hashCode());

        System.out.println("phoneNumber2.hashCode:" + phoneNumber2.hashCode());

        System.out.println("------------------");

        System.out.println("phoneNumber1.equals(phoneNumber2):" + phoneNumber1.equals(phoneNumber2));

        Map<PhoneNumber,String> map = new HashMap<PhoneNumber, String>();

        map.put(phoneNumber1,"zhangsan");

        System.out.println("map contains phoneNumber1:" + map.containsKey(phoneNumber2));

        String name = map.get(phoneNumber2);

        System.out.println("name:" + name);

    }

}

 

创建PhoneNumber 的两个实例,先比较他们的equals,然后将第一个实例加入hashMap,然后判断HashMapcontainsKey方法及get方法(入参是第二个实例),控制台输出如下:

phoneNumber1.hashCode:24355087

phoneNumber2.hashCode:5442986

------------------

phoneNumber1.equals(phoneNumber2):true

map contains phoneNumber1:false

name:null

 

导致这个原因是因为覆盖了equals方法,没有同时覆盖hashCode方法,HashMap实现在putgetcontainsKey等方法时会计算keyhash,具体HashMap的原理可以参看博客:

http://www.cnblogs.com/xwdreamer/archive/2012/06/03/2532832.html

 

已经讲得很详细了,不重复劳动了。

 

书中提出的一种简单的hashCode生成方式如下:

1、把某个非0的常数值,比如说17,保存在一个名为resultint类型的变量中;

2、对于对象中的每个关键域f(equals方法中涉及的每个域),完成以下步骤:

  a.为该域计算int类型的散列码c

    .如果该类型是boolean类型,则计算(f ? 1 : 0);

    .如果该域是bytecharshortint类型,则计算(int)f

    .如果该域是long类型,则计算(int)(f ^ (f >>> 32));

    .如果该域是float类型,则计算Float.floatToIntBits(f);

    .如果该域是double类型,则计算Double.doubleToLongBits(f),然后按照步骤

          2.a.,为得到的long类型的值计算散列值;

    .如果该域是一个对象引用,并且该类的equals方法通过递归的调用equals的方式

          来比较这个域,则同样为这个域递归的调用hashCode,如果这个域的值为null,

          返回0

    .如果该域是一个数组,则要把每一个元素当做单独的域来处理。如果数组域中 

          的每个元素都很重要,可以使用Arrays.hashCode方法。

  b.按照如下公式,2.a中计算得到的散列码c合并到result中:

      result = 31 * result + c;

3、返回result

以上规则能记住当然最好,但是工作中我们用的IDE往往为我们提供了这些功能,以下代码是在IDEA中自动生成的:

@Override

public int hashCode() {

    int result = (int) areaCode;

    result = 31 * result + (int) prefix;

    result = 31 * result + (int) lineNumber;

    return result;

}

 

跟以上规则是类似的。

 

再来运行下之前的测试:

phoneNumber1.hashCode:1026

phoneNumber2.hashCode:1026

------------------

phoneNumber1.equals(phoneNumber2):true

map contains phoneNumber1:true

name:zhangsan

 

我们来看下如下的测试:

/**

 * Created with IntelliJ IDEA.

 * User: [email protected]

 * Date: 2014-08-20 15:25

 * Description:

 */

public class PhoneNumberTest {

    public static void main(String[] args) {

        PhoneNumber phoneNumber1 = new PhoneNumber(1,2,3);

 

        Map<PhoneNumber,String> map = new HashMap<PhoneNumber, String>();

        map.put(phoneNumber1,"zhangsan");

 

        System.out.println("map contains PhoneNumber(1,2,3) before update:" + map.containsKey(new PhoneNumber(1,2,3)));

 

        System.out.println("----------------------");

 

        for (PhoneNumber phoneNumber : map.keySet()){

            System.out.println("before update hash code:" + phoneNumber.hashCode());

            phoneNumber.setAreaCode((short) 4);

            System.out.println("after update hash code:" + phoneNumber.hashCode());

        }

 

        for (PhoneNumber phoneNumber : map.keySet()){

            System.out.println(phoneNumber.getAreaCode() + " " + phoneNumber.getPrefix() + " " + phoneNumber.getLineNumber() + " " + phoneNumber.hashCode());

        }

 

        System.out.println("map contains PhoneNumber(1,2,3) after update:" + map.containsKey(new PhoneNumber(1,2,3)));

        System.out.println("##############");

        System.out.println("map contains PhoneNumber(4,2,3) after update:" + map.containsKey(new PhoneNumber(4,2,3)));

    }

}

 

phoneNumber1 加入HashMap,然后判断map.containsKey(new PhoneNumber(1,2,3)),然后对HashMap遍历,修改keyareacode属性,以改变keyhashCode,然后再去判断map.containsKey(new PhoneNumber(1,2,3))map.containsKey(new PhoneNumber(4,2,3))

 

看下输出结果:

map contains PhoneNumber(1,2,3) before update:true

----------------------

before update hash code:1026

after update hash code:3909

4 2 3 3909

map contains PhoneNumber(1,2,3) after update:false

##############

map contains PhoneNumber(4,2,3) after update:false

 

后面两次比较都是返回false

原因:

for (PhoneNumber phoneNumber : map.keySet()){           

    System.out.println("before update hash code:" + phoneNumber.hashCode());

    phoneNumber.setAreaCode((short) 4);

    System.out.println("after update hash code:" + phoneNumber.hashCode());

}

 

这段代码执行过后,因为mapkeyareaCode值已经修改,在判断map.containsKey(new PhoneNumber(1,2,3)),由于equals方法返回false导致contains方法返回false;而在判断map.containsKey(new PhoneNumber(4,2,3))返回false是因为mapkeyhashCodenew PhoneNumber(4,2,3)hashCode值不一样,这点在前文提到的博客中有说明,map中的entry中的key会缓存计算后的hash,详细信息可以参看上文提到的博客。

 

hashCodeequals方法还是很重要的,在理解hibernate的数据库一致性与实体一致性时有涉及到。

你可能感兴趣的:(java,HashCode)