彻底理清 hashCode 和 equals 的关系

这篇文章主要是介绍 hashCode 和 equals 之间的关系。

在此之前,我们应该了解 hashCode 和 equals 的作用分别是什么?

hashCode

在 Object 类中 hashCode 的定义如下:

public native int hashCode();

这是一个由 native 修饰的本地方法

在 Java 中本地方法就是由 C / C++ 代码进行功能实现的接口,我们在 jdk 源代码中是看不到其具体实现的细节的。

该方法在没有被子类重写的默认情况下是返回调用对象在内存堆上的独特值,该值可以认为是该对象在内存堆中的身份证号,具有唯一性。

即使对象属于同一个类,对象内部属性值相同,两者所得该值也不会相同。

我们定义一个 Student 类:

public class Student {
    public String name;
    public int age;

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

进行测试

public static void main(String[] args) {

    Student s1 = new Student("猫弟",20);
    Student s2 = new Student("猫弟",20);
    System.out.println("s1 ==> " + s1.hashCode());
    System.out.println("s2 ==> " + s2.hashCode());
}

可得到结果

s1 ==> 1846274136
s2 ==> 1639705018

在此,我们还未重写 hashCode 方法,默认会继承并调用 Object 类中 hashCode 方法。

当然,计算机科学家们搞出这个哈希值概念不是为了让我们懵逼的,哈希值的优势在于可以利用它来迅速的查找某个对象。

但在一般情况下我们调用 Object 类中的 hashCode 方法是没有实际使用意义的,因为它是根据对象在内存堆中的内存地址来计算得出的一个唯一的值。

我们更在意的是对象中存储的属性值内容,在一些应用方面,例如在 HashMap 中存储元素的时候,当我们插入一个 key-value 值的时候,程序应该能自动判断插入的 key 值是否存在。

由于源码设计,hashMap 会调用 key 的 hashCode 方法计算得出 哈希值,如果此时 key 所属类没有重写 hashCode 方法,那么默认调用 Object 中的 hashCode 方法。

这样导致的结果是每一个不同 key 对象所得哈希值无论何时都不会相同,HashMap 会认为每个 key 都不相同,此时类似于 HashMap 这种基于哈希值的散列表(哈希表)也就没有其存在的意义了。

所以重写 hashCode 方法主要是为了让程序以对象中属性值为根据计算出一个哈希值,我们为上述 Student 类添加上hashCode 方法。

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

该方法会调用 Arrays 工具类中的 hashCode 方法,大家有兴趣可以进源码看一下。

当然,hashCode 毕竟是通过属性值经过一系列计算得到的,那么就可能存在不同对象计算出了哈希值相同的情况(类似于 8 + 7 == 6 + 5 + 4),这种情况被称作哈希碰撞(哈希冲突),优秀的哈希算法会尽量减少哈希碰撞的发生。

至此,我们只需要知道重写 hashCode 方法是为了类对象产生以属性为根据的哈希值就可以了。在散列表中,计算出的哈希值是判断两对象是否相同(根据属性)的第一道屏障。

equals

对于 equals 方法,在 Object 类中定义如下

public boolean equals(Object obj) {
    return (this == obj);
}

默认使用 == 比较调用对象和参数对象在内存中的地址值,只要两个对象的引用指向的不是同一个对象,那么该方法返回值一直为 false。

重写 equals 方法的目的是当产生哈希冲突时,我们可以进一步利用 equals 来对比对象属性内容是否确实相同。如果不同,认为是不同对象;如果相同,认为是同一对象。

总结

那么到这有小伙伴要说,这样不是只需要一个 equals 方法就行了吗?

先说结论,hashCode 和 equals 只有在散列表(哈希表)中才能相辅相成,发挥作用。

一般情况下,两个方法没有任何关系。如果我们只需要对比两个对象内容是否相同,调用 equals 即可。

但在散列表中,我们需要基于 hashCode 算出元素要插入的数组下标,如果该下标下已经有值,那么说明发生了哈希冲突,这时我们再调用 equals 进行进一步的对比。

一个规定如下

当 hashCode 不同时,两个对象肯定不同

当 hashCode 相同时,需进一步调用 equals 对比

当 equals 返回 true 时,两个对象 hashCode 一定相同

所以当我们需要将某一个类的对象存入散列表时(如 HashMap、HashTable 等),hashCode 和 equals 都必须重写。

posted @ 2021-12-03 15:44  猫杰瑞  阅读(577)  评论(0)    收藏  举报