首页 > 技术文章 > 为什么hashCode和equals方法要一起重写

DarkSki 2021-12-28 19:38 原文

参考连接:https://zhuanlan.zhihu.com/p/50206657

一、问题

问题:HashSet不允许存放重复的对象,但在重写equals方法但没有重写hashCode方法的前提下,两个对象相等,哈希值不相等

代码:

import java.util.HashSet;
import java.util.Map;
import java.util.Objects;

public class HashSet_ {

    public static void main(String[] args) {

        HashSet<Object> hashSet = new HashSet<>();
        Employee em1 = new Employee("张豪", 24);
        Employee em2 = new Employee("李四", 21);
        Employee em3 = new Employee("张豪", 24);
        hashSet.add(em1);
        hashSet.add(em2);
        hashSet.add(em3);

        System.out.println(em1.equals(em3));
        System.out.println(em1.hashCode());
        System.out.println(em3.hashCode());
    }
}

class Employee {
    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

结果

进入hashCode方法,进入到Object类

//true
//356573597
//21685669

二、原因

1、hashCode的约定

当我们将equals方法重写后有必要将hashCode方法也重写,这样才能保证不违背hashCode方法中“相同对象必须有相同的哈希值”的约定

(1)什么是hashCode()方法

hashCode()方法的本质就是一个哈希函数,将对象的地址值映射未Integer类型的哈希值

要求:

  • 一个对象多次调用它的hashCode()方法,应该返回相同的哈希值(Integer)
  • 两个对象如果通过equals方法判定为相等,那么就应该返回相同的哈希值(Integer)
  • 两个地址值不相等的对象调用hashCode方法不要求返回不相等的哈希值,即对象不相等哈希值可以相等
  • 哈希值不等,必是不同的对象

(2)结论

equals方法和hashCode方法配套使用,对于任何一个对象,hashCode方法必须要完成一件事:为该equals方法认定相同发对象返回相同的哈希值,即当我们根据业务改写了equals方法时,也应当同时改写hashCode方法的实现,否则hashCode方法依然返回的是依据Object类中的依据地址得到的哈希值(Integer)

2、以String类为例,了解同时重写的目的

(1)equals方法

重写父类的equals方法,如果两个对象的地址一样或者内容一样(即地址不同,为两个对象),则认为两者相等

public boolean equals(Object anObject) {
    if (this == anObject) {//两个对象地址相同
        return true;//返回true
    }
    if (anObject instanceof String) {//如果是String型对象
        String anotherString = (String)anObject;//强制向下转换
        int n = value.length;//value为charp[]数组存放字符串
        if (n == anotherString.value.length) {//如果两个数组长度相等
            char v1[] = value;//分别记录两个数组
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {//遍历,循环n次
                if (v1[i] != v2[i])//如果有一个字符不相等
                    return false;//则两个数组不相等
                i++;
            }
            return true;//如果没有返回false,说明每个字符都相等,返回true
        }
    }
    return false;
}

如果未重写hashCode()方法,会调用父类hashCode方法来计算哈希值,即以两个字符串对象各自的地址映射为哈希值,即被equals认定相等的两个不同对象,拥有不同的哈希值,与两个对象如果通过equals方法判定为相等,那么就应该返回相同的哈希值(Integer)的约定不相符,因此我们需要重写hashCode方法,以得到这样一个结果:必须保证重写后的equals方法认定相同的两个对象拥有相同的哈希值

(2)hashCode方法

public int hashCode() {
    int h = hash;//h记录hash值
    if (h == 0 && value.length > 0) {//如果哈希值为0且数组长度大于0
        char val[] = value;

        for (int i = 0; i < value.length; i++) {//遍历数组
            h = 31 * h + val[i];//新的哈希函数返回新的哈希值
        }
        hash = h;
    }
    return h;//返回新的哈希值,哈希值由数组元素决定,数组元素相同,哈希值相同
}

此时,便保证了,重写后的equals方法认定相同的两个对象拥有相同的哈希值

由此,得到一个结论:hashCode方法的重写原则就是保证equals方法认定为相同的两个对象拥有相同的哈希值

3、为什么要保证相同对象有相同的哈希值

在HashMap中,通过key对应的哈希值找到对应的地址来存放元素,如果没有重写hashCode方法,即两个相同的key有着不同的哈希值,那么,将不能再通过key找到对应的索引,散列表的数据结构将不存在。

因此,我们可以认为hashCode方法不仅仅是与equals配套使用的,它甚至是与Java集合配套使用的

三、解决办法

在Employee类中重写equals方法和hashCode方法

class Employee {

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age &&
            Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

推荐阅读