首页 > 技术文章 > JAVA中重写equals()方法的同时要重写hashcode()方法

zcjyzh 2018-07-18 15:20 原文

案例:

比如一个人在不同的时期在系统中生成了两个实例,要想判断这两个实例是不是一个人,比较身份证号就可以了。假定这两个实例,一个是16岁时建立的档案,一个是24岁入职建立的档案,如果不重写equals方法,这两个实例肯定不是一个人了。 

 

如果不被重写(原生)的hashCode和equals是什么样的?

        1  . 不被重写(原生)的hashCode值是根据内存地址换算出来的一个值。

        2 .  不被重写(原生)的equals方法是严格判断一个对象是否相等的方法(object1 == object2)。

 

我们先来看一下Object.hashCode的通用约定(摘自《Effective Java》第45页)

  1.  在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,那么,对该对象调用hashCode方法多次,它必须始终如一地返回 同一个整数。在同一个应用程序的多次执行过程中,这个整数可以不同,即这个应用程序这次执行返回的整数与下一次执行返回的整数可以不一致。
  2.     如果两个对象根据equals(Object)方法是相等的,那么调用这两个对象中任一个对象的hashCode方法必须产生同样的整数结果。
  3.  如果两个对象根据equals(Object)方法是不相等的,那么调用这两个对象中任一个对象的hashCode方法,不要求必须产生不同的整数结果。然而,程序员应该意识到这样的事实,对于不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能。

     如果只重写了equals方法而没有重写hashCode方法的话,则会违反约定的第二条:相等的对象必须具有相等的散列码(hashCode)
     同时对于HashSet和HashMap这些基于散列值(hash)实现的类.

 

object对象中的 public boolean equals(Object obj),对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true;
注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。如下:
(1)当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true 
(2)当obj1.hashCode() == obj2.hashCode()为false时,obj1.equals(obj2)必须为false

如果不重写equals,那么比较的将是对象的引用是否指向同一块内存地址,重写之后目的是为了比较两个对象的value值是否相等。

这样如果我们对一个对象重写了euqals,意思是只要对象的成员变量值都相等那么euqals就等于true,但不重写hashcode,那么我们再new一个新的对象,
当原对象.equals(新对象)等于true时,两者的hashcode却是不一样的,由此将产生了理解的不一致,如在存储散列集合时(如Set类),将会存储了两个值一样的对象,
导致混淆,因此,就也需要重写hashcode()
举例说明: 
package com.zcj.eg002;
import java.util.*;

public class HelloWorld {
    public static void main(String[] args) {
      
        Name n1 = new Name("zcj001");
        Name n2 = new Name("zcj001");
        
        Collection c = new HashSet();
        c.add(n1);
        System.out.println("------------");
        c.add(n2);
        System.out.println("------------");
        System.out.println(n1.equals(n2));
        System.out.println("------------");
        System.out.println(n1.hashCode());
        System.out.println(n2.hashCode());
        System.out.println(c);
    }


}

class Name {
    private String id;
    
    public Name(String id) {
        this.id = id; 
    }
    
    public String toString(){
        return this.id;
    }
    public boolean equals(Object obj) {
        if (obj instanceof Name) {
            Name name = (Name) obj;
            System.out.println("***equal***"+ name.id);
            return (id.equals(name.id));
        }
        return super.equals(obj);
    }
        
    public int hashCode() {
        Name name = (Name) this;
        System.out.println("***Hash***" + name.id);
        return id.hashCode();
            
    }
}

当重写equals()和hashCode()运行的结果如下:

equals为true,hashCode的值相同

当只重写equals()不重写hashCode()运行结果如下:

equals为true,hashCode的值不同违反约定的第二条

当只重写hashCode()不重写equals()运行结果如下:

 

/**
 * https://www.cnblogs.com/chengxiao/p/6059914.html
 * 在HashMap源码中判断是否是同一个key(当发生hash碰撞时在链表中的位置是否一样)
 *      for (Entry<K,V> e = table[indexFor(hash, table.length)];
 *              e != null;
 *              e = e.next) {
 *             Object k;
 *             if (e.hash == hash &&
 *                 ((k = e.key) == key || (key != null && key.equals(k))))
 *                 return e;
 *         }
 *      分析:先判断hash值是否相同, 再判断equals是否相同 因此重写hash方法一定要重写equals
 *
 * 测试步骤1:
 *      需求:Name 类中的equals和hashCode注释
 *      结果:n1.equals(n2) // false
 *           n1.hashCode() // 1721931908
 *           n2.hashCode() // 1198108795
 *           c // [zcj001, zcj001]
 *      结论:在hashMap中判断n1和n2不同,因此保存在数组或链表中的位置不一样,所有c中是两个值
 * 测试步骤2:重写 Name 类中的equals和hashCode
 *      结果:n1.equals(n2) // true
 *           n1.hashCode() // -707576784
 *           n2.hashCode() // -707576784
 *           c // [zcj001]
 *      结论:在hashMap中判断n1和n2相同,因此保存在数组或链表中的位置一样,所有c中是一个值
 * 测试步骤3:只重写 Name 类中的equals不重写hashCode
 *      结果:n1.equals(n2) // true
 *           n1.hashCode() // 1721931908
 *           n2.hashCode() // 1198108795
 *           c // [zcj001, zcj001]
 *
 */

 

推荐阅读