你真的懂了 == 与 equals() 与 hashCode() 的区别与联系了吗?
文章目录
前言
== 与 equals() 与 hashCode() 的区别与联系可以说是 Java 的经典和高频面试题了。
这个问题好像不难,但是如果没有好好了解一下,却是真的很容易被问住。
来个自测:
如果以下这些问题你都可以非常确定的回答出来,那这篇博客可以不用看了;反之,如果你对有些问题还不了解,或者模棱两可,那务必好好把这篇博客看完!
- == 与 equals() 的区别?
- 重写了 hashCode() 方法后,散列码(或者称为:哈希值)一样的两个对象一定相等吗?
- 如果没有重写 hashCode() 方法,散列码(哈希值)一样的两个对象相等吗?
- 为什么重写了 equals() 方法后,还要重写 hashCode() 方法? 如果不重写会出现什么问题?
下面首先将这3个单独介绍一下:
一、==
- 对于基本数据类型, == 比较的是值。
- 对于引用数据类型,== 比较的是内存地址。
二、equals()
-
equals() 是 Object 类提供的方法,若未重写,底层相当于是一个 == 。
-
但一般是使用重写后的 equals() 的方法,用来比较的两个对象的属性值是否相同。
-
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。 -
Java的内置类一般都已经重写了 equals 方法,比如 String 类。
-
重写一个 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()
-
hashCode() 方法是 Object 类提供的一个本地方法,用于返回对象的散列码(hash code),散列码可以是任意的整数,包括正数和负数。
-
两个相等的对象要求返回相等的散列码。
-
Object 默认提供的本地方法 hashCode() 方法,并不一定是将对象的内存地址直接当作散列码来返回;而是通过该对象的内部地址转换成的一个整数作为散列码返回。
(《Java核心技术 卷Ⅰ》11版中也说了:“每个对象都有一个默认的散列码,其值由对象的存储地址得出。”,注意,这里说的是 “由对象的存储地址得出”,而不是说 “其值是对象的存储地址”。)
(其实到底会不会直接返回内存地址当作散列码取决于运行时库和JVM的具体实现,OpenJDK默认情况下不是直接返回内存地址作为散列码,验证的话,可以看看这篇博客:Java的Object.hashCode()的返回值到底是不是对象内存地址?) -
覆盖 equals() 方法时,也要覆盖 hashcode() 方法,使得如果 x.equals(y) 返回 true ,那么 x.hashCode() 与 y.hashCode() 就必须返回相同的值。(为什么要这样做?文章后面会解释。)
-
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() 相关的问题,应该都不在话下了。
如果看完之后,还有相关问题的不知道,可以在评论区留下问题,会及时回答更新。
这里是猿兄,为你分享程序员的世界。
非常感谢各位优秀的程序员们能看到这里,如果觉得文章还不错的话,求点赞👍 求关注💗 求分享👬,对我来说真的 非常有用!!!
注: 如果猿兄这篇博客有任何错误和建议,欢迎大家留言,不胜感激!