网上总有一些说法,重写equals一定要重写hashcode,一定是这样吗?  


    严格上讲,这种说法是错误的!至少在理解上还差那么一丁点儿...


    Java规范的有说明,一般在集合类中需要重写这两个方法,而为什么不说在所有的类中重写这两个方法呢?如果真的必须是这样,那么JVM为什么不把这两个方法封装成一个方法,而保留两个方法呢?


     实际上,重写equals并不需要重写hashcode,为什么因为总有这样的说法呢,就是因为hashcode的使用上的一些问题,请看下面分析:


     JVM既然保留两个方法,必然存在它的道理,那么这两个方法就不是必然同时重写。有可能有些时候,也许就偏偏只能重写一个方法...


   

  这个问题的根本就在于hashcode的使用问题

     假如你能够确定,不需要使用hashcode,那么你就无需重写hashcode。


比如下面代码就完全不需要重写

public class Student {
 private String id;
 private String name;
 public Student(String id, String name) {
     this.id = id;
     this.name = name;
 
 }  
 public boolean equals(Object obj) {
 if(obj instanceof Student) {
 Student s = (Student)obj;
 return s.id.equals(id) && s.name.equals(name);
 }
 return false;
 } 
    public static void main(String[] args) {
 Student s1 = new Student("001", "小明");
 Student s2 = new Student("002", "小刚");
 Student s3 = new Student("001", "小明");
 System.out.println(s1.equals(s2));
 System.out.println(s3.equals(s1));
}



那么什么时候需要考虑该不该重写呢?既然要考虑同时重写hashcode和equals,必然是二者同时使用的情况

   为什么会需要考虑这些呢,这只能怪JVM了,虚拟机有很多同时使用hashcode和equals这两个方法的一些类,如果你使用了这些类,那么你就必须同时考虑equals和hashcode 的业务逻辑。

比如HashMap put方法最终调用了这样一段代码


if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))


      ...


     

HashMap往里面put值的时候,使用到了hashcode,而实现逻辑就是先判断hashcode,再比较引用,最后比较equals....


    看!这里就同时使用了hashcode和equals,如果你使用了hashmap,那么你就必须同时考虑equals和hashcode了,HashMap的put的Key的行为会同时受到这两个方法的影响。我们可以看到,只有在hash相同时,才进行equals比较,也就是说即使equals判定为相同,而hash不同,此段逻辑依旧为false


    究竟要不要重写,只是这里你是想要为true还是false的问题,当然一般业务上偏true额比较多。


Hashset继承于HashMap,所以也需要考虑上述问题


请看如下代码: 

public static void main(String[] args) {
 Student s1 = new Student("001", "小明");
 Student s2 = new Student("002", "小刚");
 Student s3 = new Student("001", "小明");
 System.out.println(s1.equals(s2));
 System.out.println(s3.equals(s1));
 Set<Student> set = new HashSet<Student>();
 set.add(s1);
 set.add(s2);
 set.add(s3);
 System.out.println(set);
}
}

    要根据value进行去重,就是根据equals来做判断是否相同的依据,那么你就必须重写HashCode。如果你想根据引用判断是否是同一个Student,即根绝"=="作为是否与重复的依据,那么就必须不能重写HashCode。


    如上述代码:重写HashCode之后是两个元素,不重写HashCode,那么集合里就是三个元素。需不需要重写,JVM是不知道的,需要你自己去判断。


    一般情况下,很多框架经常会使用很多集合,可能需要考虑这个问题。

我这里再举一个使用中的例子,比如有如下需求:
      有一个SuperPerson超人类,里面有一个变身方法,变身后可以进入战斗模式,而equals方法用于判断两个超人之间的战斗力是否相同,而并不用于表示是不是同一个人,那么这种情况,战斗力是否相同,用equals判断,而是不是同一个人,要用“==”判断,那么此需求,就需要重写equals,而不能重写hashcode
      现在有一大批超人类的引用,也就是超人的名单,可能重复,有的可能指的是是同一个人,有的不是,总之很多。当发生一个消息:怪物来了,所有超人变身,进入战斗模式!现规定每一个超人只能变身一次,而且所有超人必须进入战斗模式。
实现方法如下:
     该需求就要重写euqals方法,并且不能重写hashcode方法,并使用hashset实现。把所有的超人放到一个hashset中去,循环遍历set,调用change方法,通知变身,这就是所谓的观察者模式。
      当使用观察者模式时,即使观察的对象重写了equals方法,使用了Hashset,也必须不能重写hashcode方法,否则会有对象观测不到通知

    总结:

    因为JVM中,有同时使用equals和hashcode的实现类,如果你使用到了,就需要考虑二者返回值一致性的问题,即重写问题。一般情况下,同时重写这两个总是对的,因为JVM中有一个不成文的约束,就是两个对象value相同,即equal,那么其hashcode也要相同,很多实现类如Hashmap等都有这种约束!所以我们也要入乡随俗,同时重写这两个方法,代码和java系统库的风格一致。当然你非要实现一种equals判断为true而hashcode不同,即hashcode和是否"=="一致,也不是不可以,只不过别人理解起来可能怪怪的。

     看到这里如果你能够回答上如下问题,就表明你理解了本文:

     问:hashcode和equals有什么关系?