Object类中有这样一段说明,意思是建议我们equals方法和hashCode方法,或者一起重写,或者一起不重写,以维护hashCode的常规协定。

   什么叫hashCode的常规协定呢?

   我的理解就是:两个对象通过equals方法进行比较相等,它们分别调用hashCode方法时一定返回相同的整数;两个对象通过equals方法比较不相等是,不要求它们调用hashCode方法时必须返回不同的值,但是程序员应该意识到,在这种情况下,让它们返回不同的可以提高哈希表的性能。

   怎么又涉及到哈希表的性能呢?

   其实,我们一般不会直接调用hashCode方法,但是在集合中它们会被频繁的使用。重写equals方法时建议重写hashCode方法,是针对像Map、Set这种集合操作而言的。

   我们创建一个类,谁都不能保证它什么时候会被放到集合中,所以,为了避免不必要的麻烦,建议重写equals方法的同时也重写hashCode方法,重写hashCode方法的同时也一起重写equals方法。

   其实,equals方法与hashCode方法不一致会带来两个比较明显的问题。

   一、理解困难,容易混淆

   二、查找效率低下,也就是所谓的哈希表的性能会降低


   这又从何说起呢?

   我们知道,HashMap底层维护的是一个Entry类型的数组,向HashMap中添加键值对时,首先通过Key值计算出一个位置,该位置可能就是此元素将要放置的位置。如果发现,这个位置上没有元素,也就是为null,那么就直接将这个待添加的元素放置在该位置上。如果这个位置上已经有元素存在,那么接着通过equals方法比较这个待添加的元素Key与已经存在的这个元素的Key是否相等,不相等则执行添加操作,相等则返回旧值不执行添加操作。注意,这种情况下执行添加操作是将待添加的元素放置这个位置上,然后让这个待添加的元素指向已经存在的这个旧元素,如此在这个位置上就形成了链表。同理,取出元素是和添加类似,是通过给定对象的hashCode()方法计算出一个位置,然后在该位置上进行查找,找到就返回该元素,否则返回null。

   明白了这个过程就好办了。

   如果equals方法与hashCode方法一致,那么在HashMap所维护的那个Entry类型的数组中的每个位置上就只有一个元素,而不会形成链表,这样一来查找的性能就非常好,同时理解上也十分清晰明了。反之,如果二者不一致,就有可能造成难以理解或者查找的性能比较低。

下面以String类举例说明:

  • 假设String类只重写了equals()方法,而没有重写hashCode()方法。也就是说其hashCode()方法仍然继承Object类的hashCode()方法。

    看下面的代码段

   HashSet set = new HashSet();

   String s1 = new String("hello");

   String s2 = new String("hello");

   set.add(s1);

   set.add(s2);

   System.out.println(set.size());    //输出为2

   此时,set中有两个元素,这两个元素的内容都为"hello",它们存储在数组中的不同位置上,因为

   两个对象的hashCode()返回值不同。

   如果我们将set中的元素迭代出来,就会看到两个"hello"。这就造成了理解上困难了,因为在我

   们的记忆中set中的元素是不能重复的,但现在这个HashSet对象中却有两个"hello",这怎么解释

   呢?虽然,它们实际上是两个不同的对象,但从表象上看它们确实是“一样的”,也只有构造这        个HashSet对象的我们知道,但是其它调用者却很难理解,这就是所谓的理解上的混乱。

  • 假如String类只重写了hashCode()方法,而没有重写hashCode()方法。

    同样,我们看上面那段代码

    set中仍然有两个元素,但是相对于HashMap底层的那个Entry数组来说,这两个元素在相同的位置上,并且形成了链表,s2指向s1,因为s1和s2的hashCode()返回值相同,但通过equals()方法进行比较却返回false。

    这样一来,查找起来就比较费劲了,因为要遍历链表。

    我们知道,链表遍历只能是通过前一个元素找下一个元素。

    我们这里是两个对象,找起来可能不那么费力,但是对象多了呢?链表长了呢?

   这就是所谓的效率低。


这两点就造成了我们前面提到的哈希表的性能的下降。

   综上所诉,虽然不强制要求重新equals方法时必须重写hashCode方法或者是重写hashCode方法必须重写equals方法,但是为了避免不必要的麻烦,建议二者要么一起重写,要么都不重写,以保持它们的一致性。

   以上是作者的一点点理解,如有纰漏之处,还请多多指教。