前言

== 与 equals() 与 hashCode() 的区别与联系可以说是 Java 的经典和高频面试题了。
这个问题好像不难,但是如果没有好好了解一下,却是真的很容易被问住。
来个自测:
如果以下这些问题你都可以非常确定的回答出来,那这篇博客可以不用看了;反之,如果你对有些问题还不了解,或者模棱两可,那务必好好把这篇博客看完!

  1. == 与 equals() 的区别?
  2. 重写了 hashCode() 方法后,散列码(或者称为:哈希值)一样的两个对象一定相等吗?
  3. 如果没有重写 hashCode() 方法,散列码(哈希值)一样的两个对象相等吗?
  4. 为什么重写了 equals() 方法后,还要重写 hashCode() 方法? 如果不重写会出现什么问题?

下面首先将这3个单独介绍一下:

一、==

  1. 对于基本数据类型, == 比较的是值。
  2. 对于引用数据类型,== 比较的是内存地址。

二、equals()

  1. equals() 是 Object 类提供的方法,若未重写,底层相当于是一个 == 。

  2. 但一般是使用重写后的 equals() 的方法,用来比较的两个对象的属性值是否相同。

  3. Java 语言规范要求 equals 方法要满足以下五点:
    自反性: 对于任何非空引用 x ,x.equals(x) 应该返回 true 。
    对称性: 对于任何引用 x 和 y ,当且仅当 x.equals(y) 返回 true 时, y.equals(x) 返回 true 。
    传递性: 对于任何引用 x、y 和 z ,如果 x.equals(y) 返回 true , y.equals(z) 返回 true ,那么 x.equals(z) 也应该返回 true 。
    一致性: 如果 x 和 y 引用的对象没有发生变化,那么反复多次调用 x.equals(y) 应该返回同样的结果。
    对于任何非空引用 x , x.equals(null) 应该返回false。

  4. Java的内置类一般都已经重写了 equals 方法,比如 String 类。

  5. 重写一个 equals() 方法
    ① 检查是否是同一个对象的引用,如果是的话直接返回 true 。
    ② 检查是否是同一个类型,如果不是的话,直接返回 false 。
    ③ 将对象强制转换为相应类类型的变量。
    ④ 判断两个对象每个字段是否都相等,基本类型字段用 == 比较,对象字段用 equals 比较;如果所有字段都相等,返回 true ,否则返回 false 。

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

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

三、hashCode()

  1. hashCode() 方法是 Object 类提供的一个本地方法,用于返回对象的散列码(hash code),散列码可以是任意的整数,包括正数和负数。

  2. 两个相等的对象要求返回相等的散列码。

  3. Object 默认提供的本地方法 hashCode() 方法,并不一定是将对象的内存地址直接当作散列码来返回;而是通过该对象的内部地址转换成的一个整数作为散列码返回。
    (《Java核心技术 卷Ⅰ》11版中也说了:“每个对象都有一个默认的散列码,其值由对象的存储地址得出。”,注意,这里说的是 “由对象的存储地址得出”,而不是说 “其值是对象的存储地址”。)
    (其实到底会不会直接返回内存地址当作散列码取决于运行时库和JVM的具体实现,OpenJDK默认情况下不是直接返回内存地址作为散列码,验证的话,可以看看这篇博客:Java的Object.hashCode()的返回值到底是不是对象内存地址?)

  4. 覆盖 equals() 方法时,也要覆盖 hashcode() 方法,使得如果 x.equals(y) 返回 true ,那么 x.hashCode() 与 y.hashCode() 就必须返回相同的值。(为什么要这样做?文章后面会解释。)

  5. Java的内置对象,一般都重写了

四、知道了 == 、equals()、hashCode() 基本概念之后,我们来看看博客开始的几个问题:

4.1 == 与 equals() 的区别?

(这个不难,根据上面的内容,分别说出 == 与 equals 的概念也就是说出了区别了。)

4.2 重写了 hashCode() 方法之后,散列码一样的两个对象一定相等吗?

重写了 hashCode() 方法之后,不同的两个对象的散列值可能相同。因为基于方法的实现,不同的对象经过方法计算出的值可能一样(假设 计算方法是 hash%3,而 5%3=2 ,8%3=2 ,两个不同的 hash%2 取余都是 2 )。

再比如,我们来看一下 Java的 String 类中的 hashCode() 的实现:

String类的 hashCode() 就会出现不同的对象,但是散列值不同:

4.3 如果没有重写 hashCode() 方法散列码一样的两个对象一定相等吗?

如果没有重写 hashCode() 方法,不同的两个对象的散列值依旧可能相同;意思也就是说,散列值相同的两个对象可能不是同一个对象。因为 Object 类的默认的 hashCode 方***从对象的存储地址得出散列码,它是通过对象的内存地址转换成一个整数来实现的,而不是直接将内存地址当作散列值。

4.4 为什么重写了 equals() 方法,还要重写 hashCode() 方法?如果不重写会出现什么问题?

如果只重写 equals 方法,而不重写 hashCode() 方法,在向集中添加元素时,可能会使集中出现相同值的元素。

下面来测试一下看看:

① equals() 和 hashCode() 方法都重写

首先我们写一个学生类,有两个字段(id 和 name),重写 equals() 和 hashCode() 方法,为了方便我们查看结果,顺便把 toString方法重写一下。

然后我们在测试类中去测试一下:

可以看到,,虽然 student1 与 student2 两个对象的是两个不同的对象,但是他们的 id 和 name 都一样,重写了 equals() 和 hashCode() 方法后 ,这两个对象调用 equasls 比较返回的是true,并且这两个对象的散列码也一样;插入到 studentSet 集中时,也只能插入一个。

②只重写 equals() 方法,不重写 hashCode() 方法

还是刚刚的代码,但是这次我们只重写了 equals() 方法,而没有重写 hashCode() 方法。

同样的测试类再测试一下:

这两个对象调用 equals() 方法返回的还是 true,但是这两个的散列码却不同,插入到集中的时候,明明两个对象的字段值一模一样,但是可以插入到一个集中!!这就违反了集的定义了(集中存放的是不重复的数据)。
不仅集 hashSet 中可能会遇到这样的问题,其他用到 散列码 的地方也可能会遇到这样的问题。

所以如果重写了 equals() 方法,务必记得还要重写 hashCode() 方法,不如你的程序可能会出现意想不到的问题!


了解了这些之后,回答 == 与 equals() 与 hashCode() 相关的问题,应该都不在话下了。

如果看完之后,还有相关问题的不知道,可以在评论区留下问题,会及时回答更新。

这里是猿兄,为你分享程序员的世界。

非常感谢各位优秀的程序员们能看到这里,如果觉得文章还不错的话,求点赞👍 求关注💗 求分享👬,对我来说真的 非常有用!!!

注: 如果猿兄这篇博客有任何错误和建议,欢迎大家留言,不胜感激!