== 和 equals() 到底谁管谁?你真的分得清吗?
开篇语
哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛
今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。
我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!
一、前言
说实话,只要是写 Java 的,几乎都经历过这样的灵魂拷问:“为啥我明明两个对象内容一样,== 比较就是 false?”、“equals() 要不要重写?”、“重写了 equals(),hashCode() 要不要一起上?”
如果这些问题你心里都“差不多懂,但又说不太清”,那我们就聊一次透一点的。今天就不讲那些死板定义,我们用内存、对象、集合、代码例子一步一步把 == 和 equals() 讲清楚,再带你把 equals 重写规范打包带走,写代码心里有谱,不再靠猜。😎
二、为什么会同时存在 == 和 equals()?
先丢一个直观的结论:
-
==:比较的是“是不是同一个东西”- 基本类型:比较数值本身
- 引用类型:比较是不是同一个对象(同一块内存地址)
-
equals():比较的是“看上去是不是一样”- 默认实现:和
==一样 - 但很多类(比如
String、包装类、集合)重写了equals(),用来比较“内容逻辑相等”
- 默认实现:和
你可以把它俩想成:
==:身份证号一样吗?(是人类编号)equals():长得是不是一样?信息是不是一样?
三、== 到底干了啥?
1. 基本类型:值比较
int a = 10;
int b = 10;
System.out.println(a == b); // true
很直白,int 是基本类型,== 比的是数值本身,10 和 10 肯定相等。
2. 引用类型:内存地址比较
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false
System.out.println(s1.equals(s2)); // true
解释一下:
new String("hello")是两块独立的对象内存s1 == s2比较的是“是不是同一个对象”,显然不是equals()被String重写成“按内容比较”,所以s1.equals(s2)是true
所以结论:引用类型用 ==,你问的是“是不是同一个对象”;用 equals(),你问的是“内容是不是一样”。
四、equals() 的真面目:默认实现 VS 重写实现
1. Object 中的默认实现
在 java.lang.Object 里,equals() 默认就是:
public boolean equals(Object obj) {
return (this == obj);
}
也就是说,如果某个类没有重写 equals(),那你调用的其实和 == 一模一样,都是比地址。
2. 常见类是怎么重写 equals() 的?
比如:
String:按字符串内容比较- 包装类(
Integer、Long等):按数值比较 - 集合类(
ArrayList、HashSet):按元素内容比较
来看一个典型差异示例 👇
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s2); // true(字符串常量池复用)
System.out.println(s1 == s3); // false(new 出新对象)
System.out.println(s1.equals(s3)); // true(按内容比)
坑点: 有些人看到 s1 == s2 是 true,就误以为以后比字符串都可以用 ==,那是因为 字符串常量池 在偷偷帮你省事,一旦涉及到 new 或从外部读取,就很容易翻车。
五、一个经常坑人的例子:Integer 缓存
Integer a = 100;
Integer b = 100;
Integer c = 200;
Integer d = 200;
System.out.println(a == b); // true(在 -128 ~ 127 缓存范围内)
System.out.println(c == d); // false(超出缓存范围)
System.out.println(c.equals(d)); // true(值相等)
Integer对 -128 ~ 127 的值做了缓存,小数值可能“看起来像是同一个对象”- 超出范围就重新 new,因此
c == d为false
所以:
想比数值,用
equals();想比是不是一个对象,用==。
拿==去比包装类,很容易写出“时好时坏”的诡异代码。
六、实战:哪些地方必须用 equals(),不能偷懒?
1. 比较字符串内容
String input = getUserInput(); // 来自用户输入 / 网络 / 文件...
if ("yes".equals(input)) {
System.out.println("用户选择了 YES");
}
这里有两个关键点:
- 用
equals()比内容 - 用
"yes".equals(input)避免input为null时抛NullPointerException
对比一下这段“炸弹版”代码:
if (input.equals("yes")) { // input 为 null 时直接 NPE
...
}
2. 比较业务对象是否“相同”
比如你有一个 User 类,只要 id 一样,就认为是同一个用户。
那就该重写 equals(),而不是用 ==。
七、重头戏:equals() 的重写规范到底有哪些?
说白了,重写 equals() 不是“随便写写”的工具方法,而是一个有严格契约的东西。
1. equals() 必须遵守的 5 大原则(官方契约)
-
自反性(reflexive)
- 对任何非
null值x,x.equals(x)必须是true
- 对任何非
-
对称性(symmetric)
- 对任何
x、y,如果x.equals(y)是true,那y.equals(x)也必须是true
- 对任何
-
传递性(transitive)
- 如果
x.equals(y)为true且y.equals(z)为true,那么x.equals(z)也必须是true
- 如果
-
一致性(consistent)
- 多次比较结果应保持一致,只要参与比较的字段没变,
x.equals(y)就不能时 true 时 false
- 多次比较结果应保持一致,只要参与比较的字段没变,
-
对
null的约定- 对任何非
null的x,x.equals(null)必须是false
- 对任何非
2. 千万别忘:重写 equals() 必须同时重写 hashCode()
规则:只要你重写了
equals(),就必须重写hashCode()。
原因很简单:
-
HashMap、HashSet、HashTable等基于哈希的集合会同时用到hashCode()和equals() -
逻辑上相等的两个对象(
equals == true),如果hashCode()不一样,就会导致:- 同样元素加入
HashSet却出现多个重复 HashMap找不到刚刚 put 进去的 key
- 同样元素加入
简单粗暴一句:
equals 决定“内容是否相等”,hashCode 决定“放在哪个桶里”。内容相等但桶不一样,集合就晕了。
八、实战:一个规范版的 equals() + hashCode()
来写一个经典的 Person 类:按 id + name 判断是否相等。
import java.util.Objects;
public class Person {
private Long id;
private String name;
private int age;
public Person(Long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
// 1. 引用相等,直接 true
if (this == o) return true;
// 2. null 或类型不同,直接 false
if (o == null || getClass() != o.getClass()) return false;
// 3. 强转类型
Person person = (Person) o;
// 4. 按关键字段比较
return age == person.age &&
Objects.equals(id, person.id) &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
// 5. 使用 Objects.hash 保证与 equals 字段一致
return Objects.hash(id, name, age);
}
}
这里有几个关键点:
-
if (this == o) return true;- 优化性能,也符合自反性
-
getClass() != o.getClass()vsinstanceofgetClass():严格要求类型完全一致instanceof:允许子类被认为“相等”,但设计不好容易破坏对称性、传递性- 一般业务实体类推荐用
getClass()
-
用 同一批字段 来实现
equals()和hashCode()
九、一个错误示例:看着没事,其实很危险
看这段代码,你能找出它的问题吗?
public class User {
private Long id;
private String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return Objects.equals(id, user.id);
}
// 注意:这里没有重写 hashCode()
}
表面上:
equals()的逻辑是:只要id相同,认为是同一个用户
但是:
- 没有重写
hashCode() - 导致两个
id相同的User,equals()返回true,但hashCode()却不同(继承自 Object,等于内存地址)
在 HashSet 中就会翻车:
User u1 = new User(1L, "Tom");
User u2 = new User(1L, "Jerry");
Set<User> set = new HashSet<>();
set.add(u1);
set.add(u2);
System.out.println(set.size()); // 你以为是 1,结果是 2
这就是典型的:
equals()说你俩一样,hashCode()说你俩不是一路人,集合直接被玩晕。
十、数组、浮点数、集合里的 equals 怎么玩?
1. 数组不能直接用 equals() 比较内容
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println(a.equals(b)); // false,引用比较
System.out.println(Arrays.equals(a, b)); // true,内容比较
如果你在 equals() 里有数组字段,一定要记得用:
Arrays.equals(array1, array2);
2. 浮点数比较:尽量用 Double.compare / 容差比较
double x = 0.1 + 0.2;
double y = 0.3;
System.out.println(x == y); // 可能为 false
System.out.println(Double.compare(x, y)); // 0 表示相等
在 equals() 里可以这样写:
Double.compare(this.price, other.price) == 0;
或者用误差容忍:
Math.abs(x - y) < 1e-6;
十一、实用小技巧:写 equals() 时的一套“模板动作”
以后你只要记住这套“模板流程”,基本不会翻车:
- 写上
@Override,不让自己拼错方法签名 - 第一行写:
if (this == o) return true; - 第二步:
if (o == null || getClass() != o.getClass()) return false; - 强转:
MyClass other = (MyClass) o; - 用
Objects.equals()按字段一个个比 - 同时重写
hashCode(),字段保持一一对应
真的懒得写的话,用 IDE(IntelliJ IDEA / Eclipse)自动生成 equals/hashCode,保证:
- 字段勾选对了,逻辑就八九不离十
十二、最后帮你快速总结一遍(可以拿小本记了 📝)
1. 什么时候用 ==?
- 比基本类型的值
- 判断两个引用是否指向同一个对象
- 判断
obj == null
2. 什么时候必须用 equals()?
- 比较字符串内容
- 比较包装类型内容(
Integer、Long等) - 比较自定义业务对象是否“意义上相同”
- 在集合(
List、Set、Mapkey)里判断元素是否存在
3. 重写 equals() 要记住:
- 遵守 5 大契约:自反、对称、传递、一致、对 null 为 false
- 一定要同时重写
hashCode() - equals 和 hashCode 基于同一批字段
- 注意数组用
Arrays.equals,浮点数用Double.compare
4. 一句“顺口溜”帮你记:
“引用同不同时问
==,内容像不像交给equals();
同改equals()不改hashCode(),集合分分钟整死你。”
… …
文末
好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。
… …
学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!
wished for you successed !!!
⭐️若喜欢我,就请关注我叭。
⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。
版权声明:本文由作者原创,转载请注明出处,谢谢支持!
- 点赞
- 收藏
- 关注作者
评论(0)