覆写equals方法为什么需要覆写hashCode方法

时间:2022-10-22 16:19:39

  覆写equals方法必须覆写hashCode方法,是JDK API上反复说明的,不过为什么要这样做呢?这两个方法之间有什么关系呢?

void test() {
    // Person类的实例作为Map的key 
    Map<Person, Object> map = new HashMap<Person, Object>();
    map.put(new Person("张三"), new Object()); 
        
    // Person类的实例作为List的元素 
    List<Person> list = newArrayList<Person>()
    list.add(new Person("张三")); 
 
    // 列表中是否包含 
    boolean b1 = list.contains(new Person("张三")); 
    // Map中是否包含 
    boolean b2 = map.containsKey(new Person("张三"));
    System.out.println(b1 + "|" + b2);
}

Person类:

public class Person {
    private String name;
    private String age;
   
    public Person(String name) {
        setName(name);
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAge() {
        return age;
    }
    public void setAge(String age) {
        this.age = age;
    }
   
    @Override
    public boolean equals(Object o) {
        /*
         * 使用getClass是为了防止子类被判断为父类的情况
         * 例如:Person中覆写equals方法 {return name.equals(p.getName);}
         *     子类Employee继承Person,此时子类中的euqals方法super.equlas(p);
         * 此时如果使用instanceof关键字,因为Employee是Person的子类,所以返回true
         *  进一步调用equals返回的是true,所以会将Person和Employee认为是同一人,显然不合理
         */
        if(o != null && o.getClass() == this.getClass()) {
            Person p = (Person) o;
            if(p.getName() == null && this.name == null)
                return false;
            return this.name.equals(p.getName());
        }
        return false;
    }
}

 

  我们先来看b1,Person类的equals覆写了,不再判断两个地址是否相等,而是根据人员的姓名来判断两个对象是否相等,所以不管我们的 new Person(“张三”)产生了多少个对象,它们都是相等的。把“张三”对象放入List中,再检查List中是否包含,那结果肯定是true了。

  接着来看b2,我们把张三这个对象作为了Map的键(Key),放进去的对象是张三,检查的对象还是张三,那应该和List的结果相同了,但是很遗憾,结果是false。原因何在呢?

  原因就是HashMap的底层处理机制是以数组的方式保存Map条目(Map Entry)的,这其中的关键是这个数组下标的处理机制:依据传入元素hashCode方法的返回值决定其数组的下标,如果该数组位置上已经有了Map条目,且与传入的键值相等则不处理,若不相等则覆盖;如果数组位置没有条目,则插入,并加入到Map条目的链表中。同理,检查键是否存在也是根据哈希码确定位置,然后遍历查找键值的。

  接着深入探讨,那对象元素的hashCode方法返回的是什么值呢?它是一个对象的哈希码,是由Object类的本地方法生成的,确保每个对象有一个哈希码(这也是哈希算法的基本要求:任意输入k,通过一定算法f(k),将其转换为非可逆的输出,对于两个输入k1和k2,要求若k1=k2,则必须 f(k1)=f(k2),但也允许k1≠k2,f(k1)=f(k2)的情况存在)。

  那回到我们的例子上,由于我们没有重写hashCode方法,两个张三对象的hashCode方法返回值(也就是哈希码)肯定是不相同的了,在HashMap的数组中也就找不到对应的Map条目了,于是就返回了false。

  问题清楚了,修改也非常简单,重写一下hashCode方法即可,代码如下:

public int hashCode() {
     //其中HashCodeBuilder是org.apache.commons.lang.builder包下的一个哈希码生成工具,
     //使用起来非常方便,诸位可以直接在项目中集成。
     return new HashCodeBuilder().append(name).toHashCode();
}

 

  其中HashCodeBuilder是org.apache.commons.lang.builder包下的一个哈希码生成工具,使用起来非常方便,诸位可以直接在项目中集成。(为什么不直接写hashCode方法?因为哈希码的生成有很多种算法,自己写麻烦,事儿又多,所以采用拿来主义是最好的方法。)