[转载]hashCode和equals

Java.lang.Object
有一个hashCode()和一个equals()方法,这两个方法在软件设计中扮演着举足轻重的角色。在一些类中覆写这两个方法以完成某些重要功能。本文描述了为什么要用hashCode(),
如何使用,以及其他的一些扩展。阅读本文需要有基本的hash算法知识以及基本的Java集合知识,本文属于菜鸟入门级讲解,大神读至此请点击右上角的X,以免浪费您的时间^_^。

Java.lang.Object
有一个hashCode()和一个equals()方法,这两个方法在软件设计中扮演着举足轻重的角色。在一些类中重写这两个方法以完成某些重要功能。

原文地址:hashCode和equals作者:银冰冷月

WHY hashCode()?

集合Set中的元素是无序不可重复的,那判断两个元素是否重复的依据是什么呢?
“比较对象是否相等当然用Object.equal()了”,某猿如是说。但是,Set中存在大量对象,后添加到集合Set中的对象元素比较次数会逐渐增多,大大降低了程序运行效率。
Java中采用哈希算法(也叫散列算法)来解决这个问题,将对象(或数据)依特定算法直接映射到一个地址上,对象的存取效率大大提高。这样一来,当含有海量元素的集合Set需要添加某元素(对象)时,先调用这个元素的hashCode(),就能一下子定位到此元素实际存储位置,如果这个位置没有元素,说明此对象时第一次存储到集合Set,
直接将此对象存储在此位置上;若此位置有对象存在,调用equal()看看这两个对象是否相等,相等就舍弃此元素不存,不等则散列到其他地址。

  • 为什么要用 hashCode()?
    集合Set中的元素是无序且不可重复的,那判断两个元素是否重复的依据是什么呢?
    有人说:比较对象是否相等当然用Object.equal()了。但是,Set中存在大量对象,后添加到集合Set中的对象元素比较次数会逐渐增多,大大降低了程序运行效率。
    Java中采用哈希算法(也叫散列算法)来解决这个问题,将对象(或数据)依特定算法直接映射到一个地址上,对象的存取效率大大提高。
    这样一来,当含有海量元素的集合Set需要添加某元素(对象)时,先调用这个元素的hashCode(),就能一下子定位到此元素实际存储位置,如果这个位置没有元素,说明此对象是第一次存储到集合Set,
    直接将此对象存储在此位置上;若此位置有对象存在,调用equal()看看这两个对象是否相等,相等就舍弃此元素不存,不等则散列到其他地址。
    这也是为什么set集合存储对象类型数据的时候,要不仅仅重写对象的hashCode()方法还要重写equals()方法的原因。
  • HOW use hashCode()?
    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求余数直接找到存放的位置了。

HOW use hashCode()?

Java语言对猿设计equal()有五个必须遵循的要求。

  1. 对称性。若 a.equal(b) 返回”true”, 则 b.equal(a) 也必须返回 “true”.
  2. 反射性。a.equal(a) 必须返回”true”.
  3. 传递性。若a.equal(b) 返回 “true”, 且 b.equal(c)返回 “true”,
    则c.equal(a)必返回”true”.
  4. 一致性。若a.equal(b) 返回”true”, 只要a,
    b内容不变,不管重复多少次a.equal(b)必须返回”true”.
  5. 任何情况下,a.equals(null),永远返回是“false”;a.equals(和a不同类型的对象)永远返回是“false”.

hashCode()的返回值和equals()的关系.

  1. 如果a.equals(b)返回“true”,那么a和b的hashCode()必须相等。
  2. 如果a.equals(b)返回“false”,那么a和b的hashCode()有可能相等,也有可能不等。

下面是一个例子。在实际的软件开发中,最好重写这两个方法。

public class Employee {
    int        employeeId;
    String     name;

    // other methods would be in here 

    @Override
    public boolean equals(Object obj)
    {
        if(obj==this)
            return true;
        Employee emp=(Employee)obj;
        if(employeeId.equals(emp.getEmployeeId()) && name==emp.getName())
            return true;
        return false;
    }

    @Override
    public int hashCode() {
        int hash = 1;
        hash = hash * 17 + employeeId;
        hash = hash * 31 + name.hashCode();
        return hash;
    }
}

下面着重介绍一下常用类的hashCode()实现方法。

  1. 如果a.equals(b)返回“true”,那么a和b的hashCode()一定相等。
  2. 如果a.equals(b)返回“false”,那么a和b的hashCode()有可能相等,也有可能不等。
    下面是一个例子。在实际的软件开发中,最好重写这两个方法。

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)进行排序

String类的hasCode()

Java代码

public int hashCode() {
    int h = hash;
    if (h == 0) {
        int off = offset;
        char val[] = value;
        int len = count;

            for (int i = 0; i < len; i++) {
                h = 31*h + val[off++];
            }
            hash = h;
        }
        return h;
    }

这段代码最有意思的还是hash的实现方法了。最终计算的hash值为:

s[0]31n-1 + s[1]31n-2 + … + s[n-1]

s[i]是string的第i个字符,n是String的长度。那为什么这里用31,而不是其它数呢?

31是个奇素数,如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算。使用素数的好处并不是很明显,但是习惯上都使用素数来计算散列结果。31有个很好的特性,就是用移位和减法来代替乘法,可以得到更好的性能:31*i==(i<<5)-i。现在的VM可以自动完成这种优化。(From
Effective Java)


Object类的hasCode()

Object类中hashCode()是一个Native方法。Native方法如何调用?

public native int hashCode();

Object类的Native方法类可在这里找到。
深入分析请看另外一篇博客

static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},
    {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},
    {"notify",      "()V",                    (void *)&JVM_MonitorNotify},
    {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};

源代码包括getClass()(See line58)等, hashCode()(See
line43)被定义为一个指向JVM_IHashCode指针。

jvm.cpp中定义了JVM_IHashCode(line
504)函数,
此函数里调用ObjectSynchronizer::FastHashCode,其定在 synchronizer.cpp,
可参考576行的FastHashCode 和 530行的 get_next_hash 的实现。

public class Employee {
    int        employeeId;
    String     name;

    @Override
    public boolean equals(Object obj)
    {
        if(obj==this)
            return true;
        Employee emp=(Employee)obj;
        if(employeeId.equals(emp.getEmployeeId()) && name==emp.getName())
            return true;
        return false;
    }

    @Override
    public int hashCode() {
        int hash = 1;
        hash = hash * 17 + employeeId;
        hash = hash * 31 + name.hashCode();
        return hash;
    }
}

关于Object的equals()及hashCode()

equals()和hashCode()方法是用来在同一类中做比较用的,尤其是在容器里如set存放同一类对象时用来判断放入的对象是否重复。

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

这里我们首先要明白一个问题:
equals()相等的两个对象,hashcode()一定相等,equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。换句话说,equals()方法不相等的两个对象,hashCode()有可能相等。
在这里hashCode就好比字典里每个字的索引,equals()好比比较的是字典里同一个字下的不同词语。就好像在字典里查“自”这个字下的两个词语“自己”、“自发”,如果用equals()判断查询的词语相等那么就是同一个词语,比如equals()比较的两个词语都是“自己”,那么此时hashCode()方法得到的值也肯定相等;如果用equals()方法比较的是“自己”和“自发”这两个词语,那么得到结果是不想等,但是这两个词都属于“自”这个字下的词语所以在查索引时相同,即:hashCode()相同。如果用equals()比较的是“自己”和“他们”这两个词语的话那么得到的结果也是不同的,此时hashCode()
得到也是不同的。
反过来:hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。

 


1.         equals()

在object类中,hashcode()方法是本地方法,返回的是对象的地址值,而object类中的equals()方法比较的也是两个对象的地址值,如果equals()相等,说明两个对象地址值也相等,当然hashcode()
也就相等了。

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


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

既然equals比较元素相等更准确,那么为什么还要用hashCode( )方法呢?
因为hash算法对于查找元素提供了很高的效率,如果想查找一个集合中是否包含有某个对象,大概的程序代码怎样写呢?
你通常是逐一取出每个元素与要查找的对象进行比较,当发现某个元素与要查找的对象进行equals方法比较的结果相等时,则停止继续查找并返回肯定的信息,否则,返回否定的信息,如果一个集合中有很多个元素,比如有一万个元素,并且没有包含要查找的对象时,则意味着你的程序需要从集合中取出一万个元素进行逐一比较才能得到结论。

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

Object类中定义了一个hashCode()方法来返回每个Java对象的哈希码,当从HashSet集合中查找某个对象时,Java系统首先调用对象的hashCode()方法获得该对象的哈希码表,然后根据哈希吗找到相应的存储区域,最后取得该存储区域内的每个元素与该对象进行equals方法比较;这样就不用遍历集合中的所有元素就可以得到结论,可见,HashSet集合具有很好的对象检索性能
但是,HashSet集合存储对象的效率相对要低些,因为向HashSet集合中添加一个对象时,要先计算出对象的哈希码和根据这个哈希码确定对象在集合中的存放位置为了保证一个类的实例对象能在HashSet正常存储,要求这个类的两个实例对象用equals()方法比较的结果相等时,他们的哈希码也必须相等;也就是说,如果obj1.equals(obj2)的结果为true,那么以下表达式的结果也要为true:obj1.hashCode() == obj2.hashCode()

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


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

换句话说:当我们重写一个对象的equals方法,就必须重写他的hashCode方法,不重写他的hashCode方法的话,Object对象中的hashCode方法始终返回的是一个对象的hash地址,而这个地址是永远不相等的。所以这时候即使是重写了equals方法,也不会有特定的效果的,因为hashCode方法如果都不想等的话,就不会调用equals方法进行比较了,所以没有意义了。

 

  • 大多数的数据结构通过equals方法来判断他们是否包含一个元素,例如:

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

 

List<String> list = Arrays.asList("a", "b", "c");
boolean contains = list.contains("b");

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

这个变量contains结果是true,因为,虽然”b”是不相同的实例(此外,忽略字符串驻留),但是他们是相等的。
他们通过使用一种快捷的方式(减少潜在的实例相等)进行比较,从而代替通过比较实例所包含的每个元素。而快捷比较仅需要比较下面这些方面:
快捷方式比较即通过比较哈希值,它可以将一个实例用一个整数值来代替。哈希码相同的实例不一定相等,但相等的实例一定具有有相同的哈希值。(或应该有,我们很快就会讨论这个)这些数据结构经常通过这种这种技术来命名,可以通过Hash来识别他们的,其中,HashMap是其中最著名的代表。

 


2.         hashCode()

它们通常是这样这样运作的:
当添加一个元素,它的哈希码是用来计算内部数组的索引(即所谓的桶)
如果是,不相等的元素有相同的哈希码,他们最终在同一个桶上并且捆绑在一起,例如通过添加到列表。
当一个实例来进行contains操作时,它的哈希码将用来计算桶值(索引值),只有当对应索引值上存在元素时,才会对实例进行比较。
因此equals,hashCode是定义在Object类中。

其必须遵循的约定是:


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

如果hashCode作为快捷方式来确定相等,那么只有一件事我们应该关心:相等的对象应该具有相同的哈希码,这也是为什么如果我们重写了equals方法后,我们必须创建一个与之匹配的hashCode实现的原因!
否则相等的对象是可能不会有相同的哈希码的,因为它们将调用的是Object’s的默认实现。
引用自官方文档

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

hashCode通用约定:
调用运行Java应用程序中的同一对象,hashCode方法必须始终返回相同的整数。这个整数不需要在不同的Java应用程序中保持一致。根据equals(Object)的方法来比较,如果两个对象是相等的,两个对象调用hashCode方法必须产生相同的结果。
根据equals(Object)的方法是比较,如果两个对象是不相等的,那么两个对象调用hashCode方法并不一定产生不同的整数的结果。但是,程序员应该意识到给不相等的对象产生不同的整数结果将有可能提高哈希表的性能。

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

  • HashCode实现
    下面是简单的person.hashcode()的实现:

 

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

@Override
public int hashCode() {
    return Objects.hash(firstName, lastName);
}

 

person’s是通过多个字段结合来计算哈希码的。都是通过Object的hash函数来计算。

3.         小结

  • 选择字段
    但哪些字段是相关的呢?需求将会帮助我们回答这个问题:
    如果相等的对象必须具有相同的哈希码,那么计算哈希码就不应包括任何不用于相等检查的字段。(否则两个对象只是这些字段不同但是仍然有可能会相等,此时他们这两个对象哈希码却会不相同。)所以用于哈希组字段应该相等时使用的字段的子集。默认情况下都使用相同的字段,但有一些细节需要考虑。
  • 总结
    我们了解到计算哈希码就是压缩相等的一个整数值:相等的对象必须有相同的哈希码,而出于对性能的考虑:最好是尽可能少的不相等的对象共享相同的哈希码。
    这就意味着如果重写了equals方法,那么就必须重写hashCode方法
    当实现hashCode使用与equals中使用的相同的字段(或者equals中使用字段的子集)
    最好不要包含可变的字段。对集合不要考虑调用hashCode,如果没有特殊的输入特定的模式,尽量采用通用的哈希算法

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);
    

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图