深入理解重写equals与重写hashcode

    网上总有一些说法,重写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 set = new HashSet();
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有什么关系?

      本文并不是为了讨论该不该同时重写hashcode和equals这两个方法,而是让大家深入理解为什么要考虑同时重写Hashcode和equals方法

你可能感兴趣的:(java)