[转载]hashCode和equals

原文地址: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()有什么用啊
3。你要对A类排序,有两种方法,一种就是让A类实现comparabole结构并实现compareTo()方法,那么可以通过Collections.sort(List <A>   list)对其进行排序
另一种方法:自己定义一个类B实现Comparator类并实现compare方法,
然后通过Collections.sort(List <A>   list,B   b)进行排序

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

关于Object的equals()及hashCode()

在某些时候,我们需要判断两个对象是否相等。Java的每个类都继承于Object类。它使用equals()及hashCode()这两个方法来判断两个Object是否相等。

 

1.         equals()

对于非null的对象引用,依下面关系进行判断:

l         对于任一非null引用x,x.equals(x)应返回true

l         对于任一非null引用x及y,仅在y.equals(x)返回true时,x.equals(y)才返回true

l         对于任一非null引用x、y及z,如果x.equals(y)为true,而且y.equals(z)为true,则x.equals(z)应返回true

l         对于任一非null引用x及y,如果用于比较的信息没有改变,无论多少次调用x.equals(y)都会恒定地返回true或false

l         对于任一非null引用x,x.equals(null)应返回false

 

Object的默认实现是只有在两个Object的引用相等时,才会返回true,即return x == y;

 

如果要覆盖(override)此方法,需要同时覆盖hasCode(),要求是:两个相等的对象必须有相等的hash code。

 

2.         hashCode()

其必须遵循的约定是:

l         在一个Java应用的一次运行中,如果用于equals()比较的信息未变,同一个对象的hasCode()必须恒定地返回相同的整数。但在同一个应用的另一个运行中,此值可以改变

l         如果根据equals(),两个对象均相同,则调用这两个对象的hasCode()必须返回相同的int值。

l         如果equals()返回false,这两个对象的hashCode()可以返回相同的int值。但不等的两个对象返回不同的int值可以提高hashtables的运行效率。

 

作为常理,不相等的对象的hasCode()应可能地返回不同的int值。(通常可以将对象的内存地址转换为int值后返回,但这不是Java语言所要求的)。

 

3.         小结

l         hashCode()必须配合equals()来实现判定两个对象是否相等

l         由于默认的equals()方法是使用是否同一对象的引用来判断是否相等,由于默认的hashCode()返回对象的内存地址,而不同的对象有不同的地址,因此,如果未覆盖equals(),也不需覆盖hashCode()。但有一点需注意,由于内存地址比Integer的数值要大,因此不同地址所转换成的int值可能会一样。即使出现这种情况也不要紧,仅影响hashtable的运行效率而已。

l         hashCode()的返回值必须根据用于equals()判断的信息来决定。如果equals()返回true,hashCode()必须返回相同的int值。如果equals()为false,最好返回不同的int值。

 

这样,只要符合上面3点条件,采用何种公式或方式来实现hasCode()均是可行的。

 

4.         实例

尽管道理很浅显,但要实现起来并不容易,尤其是hashCode()。考虑一下两个对象,如果它们的一个String的字段相等就可认为两个对象相等。这种情况下,应如何根据这个String的字段来返回相同或不同的int值?相等时很好办,用以下的语句即可解决:

 

if (strA.equals(strB)) {

  return 1;

}

 

但不等时呢,难点在于如何将不等的String值转换为不同的int值。而且,如果这两个对象均出自同一类,您所编写的代码均适用于这两个对象,同样的代码,如何才能出现不同的结果?如果通过硬编码的方式,先取得另一个对象的hashCode()值,再依此修改本对象的hasCode()值,势必造成每次比较都会导致本身的hasCode()值频繁发生变化。而如果同一类的不同实例使用各自的算法,从而无需提取另一个对象的hashCode()值,但在比较时,又如何实时地了解对方的情况?我们陷入了两难的境地。

 

好消息是,jakarta-commons中一个lang包,其EqualsBuilder与HashCodeBuilder类可方便地解决此问题。对于equals()及hashCode(),均有硬编码及反射机制的两种解决方法。

 

EqualsBuilder

先看码编码方式:

 

public boolean equals(Object obj) {

   if (obj instanceof MyClass == false) {

     return false;

   }

   if (this == obj) {

     return true;

   }

   MyClass rhs = (MyClass) obj;

   return new EqualsBuilder()

                 .appendSuper(super.equals(obj))

                 .append(field1, rhs.field1)

                 .append(field2, rhs.field2)

                 .append(field3, rhs.field3)

                 .isEquals();

  }

 

在进行基本的判断后,通过EqualsBulder.append()的方式,将用于区分两个对象的各个业务关键逻辑逐一增加至其实例中就可以了。

 

下面是反射机制:

 

public boolean equals(Object obj) {

   return EqualsBuilder.reflectionEquals(this, obj);

}

 

代码更简洁。

 

HashCodeBuilder

先看硬编码方式:

 

public class Person {

   String name;

   int age;

   boolean smoker;

   ...

 

   public int hashCode() {

     // you pick a hard-coded, randomly chosen, non-zero, odd number

     // ideally different for each class

     return new HashCodeBuilder(17, 37).

       append(name).

       append(age).

       append(smoker).

       toHashCode();

   }

}

 

这里,name, age, smoker等字段均为在equals()中使用的信息。任选两个随意的非零奇数作为构造函数的参数,并逐一加至HashCodeBuilder的实例中。

 

再看反射机制:

 

public int hashCode() {

   return HashCodeBuilder.reflectionHashCode(this);

}

 

同样也很简单。

 

尽管通过反映机制看起来比较简单,但一是速度较慢,二是要受到安全机制的制约,因此,应尽可能地使用硬编码的方式。

 

 

 

 

 

hashcode的作用2009年06月24日 星期三 07:13

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()有什么用啊
3。你要对A类排序,有两种方法,一种就是让A类实现comparabole结构并实现compareTo()方法,那么可以通过Collections.sort(List <A> list)对其进行排序
另一种方法:自己定义一个类B实现Comparator类并实现compare方法,
然后通过Collections.sort(List <A> list,B b)进行排序

hashCode() 是用来产生哈希玛的,而哈希玛是用来在散列存储结构中确定对象的存储地址的,(这一段在 Java编程思想 中讲的很清楚的)象util包中的 带 hash 的集合类都是用这种存储结构 :HashMap,HashSet, 他们在将对象存储时(严格说是对象引用),需要确定他们的地址吧, 而HashCode()就是这个用途的,一般都需要重新定义它的,因为默认情况下,由 Object 类定义的 hashCode 方法会针对不同的对象返回不同的整数,这一般是通过将该对象的内部地址转换成一个整数来实现的,现在举个例子来说, 就拿HashSet来说 ,在将对象存入其中时,通过被存入对象的 hashCode() 来确定对象在 HashSet 中的存储地址,通过equals()来确定存入的对象是否重复,hashCode() ,equals()都需要自己重新定义,因为hashCode()默认前面已经说啦,而equals() 默认是比较的对象引用,你现在想一下,如果你不定义equals()的话,那么同一个类产生的两个内容完全相同的对象都可以存入Set,因为他们是通过 equals()来确定的,这样就使得HashSet 失去了他的意义,看一下下面这个:


public class Test {
public static void main(String[] args) {
HashSet set = new HashSet();
for (int i = 0; i <= 3; i++){
set.add(new Demo1(i,i));
}
System.out.println(set);
set.add(new Demo1(1,1));
System.out.println(set);
System.out.println(set.contains(new Demo1(0,0)));
System.out.println(set.add(new Demo1(1,1)));
System.out.println(set.add(new Demo1(4,4)));
System.out.println(set);
}


private static class Demo1 {
private int value;


private int id;


public Demo1(int value,int id) {
this.value = value;
this.id=id;
}


public String toString() {
return " value = " + value;
}


public boolean equals(Object o) {
Demo1 a = (Demo1) o;
return (a.value == value) ? true : false;
}


public int hashCode() {
return id;
}
}
}

你分别注释掉hashCode()和 equals()来比较一下他们作用就可以拉,关键要自己动手看看比较的结果你就可以记得很清楚啦

如果还不是很明确可以再看另一个例子:


public final class Test {


public static void main(String[] args) {
Map m = new HashMap();
m.put(new PhoneNumber(020, 12345678), "shellfeng");
System.out.println(m.get(new PhoneNumber(020, 12345678)));
}


private static class PhoneNumber {

private short areaCode;



private short extension;


public PhoneNumber(int areaCode, int extension) {
this.areaCode = (short) areaCode;
this.extension = (short) extension;
}


public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof PhoneNumber)) {
return false;
}
PhoneNumber pn = (PhoneNumber) o;
return pn.extension == extension && pn.areaCode == areaCode;
}



public int hashCode() {
int result = 17;
result = 37 * result + areaCode;
result = 37 * result + extension;
return result;
}
}
}

还是那句话:你注释掉hashCode()比较一下他们作用就可以拉,关键要自己动手看看比较的结果你就可以记得很清楚啦

总结
hashCode() 方法使用来提高Map里面的搜索效率的,Map会根据不同的hashCode()来放在不同的桶里面,Map在搜索一个对象的时候先通过 hashCode()找到相应的桶,然后再根据equals()方法找到相应的对象.要正确的实现Map里面查找元素必须满足一下两个条件:
(1)当obj1.equals(obj2)为true时obj1.hashCode() == obj2.hashCode()必须为true
(2)当obj1.hashCode() == obj2.hashCode()为false时obj.equals(obj2)必须为false

Java中的集合(Collection)有两类,一类是List,再有一类是Set。你知道它们的区别吗?前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。
那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是Object.equals方法了。
但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。
也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。
哈 希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。我们可以认为hashCode方法返回的就是对象存储的物理地址(实际可能并不是,例 如:通过获取对象的物理地址然后除以8再求余,余数几是计算得到的散列值,我们就认为返回一个不是物理地址的数值,而是一个可以映射到物理地址的值)。
这 样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直 接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列 其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。


改写equals时总是要改写hashCode
============================================================
java.lnag.Object中对hashCode的约定:

   1. 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
   2. 如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。
   3. 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。


有一个概念要牢记,两个相等对象的equals方法一定为true, 但两个hashcode相等的对象不一定是相等的对象。

所以hashcode相等只能保证两个对象在一个HASH表里的同一条HASH链上,继而通过equals方法才能确定是不是同一对象,如果结果为true, 则认为是同一对象不在插入,否则认为是不同对象继续插入。

Object的代码:
public String toString () {
    return this.getClass().getName() + "@" + Integer.toHexString(this.hashCode());
}

public boolean equals (Object o) {
    return this == o;
}


public native int hashCode();


从上面我们可以看到是否很可能Object.hashCode就是代表内存地址。下面我们来证明hashcode是不是真的就是Object的内存地址呢?实际上,hashcode根本不能代表object的内存地址。
-----------------------------------------
Object.hashCode不可以代表内存地址
----------------------------------------

package com.tools;

import java.util.ArrayList;


public class HashCodeMeaning {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        int numberExist=0;
       
        //证明hashcode的值不是内存地址
        for (int i = 0; i < 10000; i++) {
            Object obj=new Object();
            if (list.contains(obj.toString())) {
                System.out.println(obj.toString() +" exists in the list. "+ i);
                numberExist++;
            }
            else {
                list.add(obj.toString());
            }
        }
       
        System.out.println("repetition number:"+numberExist);
        System.out.println("list size:"+list.size());
       
        //证明内存地址是不同的。
        numberExist=0;
        list.clear();
        for (int i = 0; i < 10000; i++) {
            Object obj=new Object();
            if (list.contains(obj)) {
                System.out.println(obj +" exists in the list. "+ i);
    

转载于:https://www.cnblogs.com/liuzhuqing/p/7480410.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值