不能直接用==判断对象逻辑相等,因其只比较内存地址;Object默认equals()也等价于==;需重写equals()和hashCode()才能实现内容比较,否则集合操作、框架交互会出错。

不能直接用 == 判断对象“逻辑相等”,根本原因在于 == 在引用类型上只看内存地址,不看内容。Object 类作为所有类的父类,它的默认 equals() 方法其实也是用 == 实现的——也就是说,没重写时,equals() 和 == 行为完全一样。
== 的语义是“引用相等”,不是“值相等”
Java 把 == 定义为语言级运算符,对引用类型而言,它只关心两个变量是否指向堆中同一块内存。哪怕两个对象字段一模一样、构造方式相同,只要不是同一个 new 出来的实例,== 就返回 false。
-
String 例子最直观:
new String("abc") == new String("abc")是 false,尽管内容相同 -
自定义对象同理:
new Person("张三", 25) == new Person("张三", 25)也是 false - 这种设计不是缺陷,而是刻意为之:== 快、确定、无歧义,适合判断“是不是同一个东西”
Object.equals() 的默认行为就是 ==
Object 类里的 equals() 方法签名是 public boolean equals(Object obj),但它的源码逻辑等价于:
return this == obj;
立即学习“Java免费学习笔记(深入)”;
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
所以你不重写,就永远在比地址。这不是方法“不好用”,而是它本来就没打算做内容比较——它把“什么叫相等”这个决定权,交给了具体业务类自己定义。
- String 重写了 equals(),所以能按字符序列比较
- LocalDateTime 重写了 equals(),所以能按时间点比较
- 你写的 Order、User、Config 等类,如果不重写,就只能比地址
不重写会出问题的典型场景
一旦进入集合操作或框架交互,仅靠 == 或未重写的 equals() 就会暴露逻辑断裂:
- HashMap 查不到:key 逻辑相同但 hashCode 不同 → 被散列到不同桶 → get() 返回 null
- HashSet 重复添加:两个“相同”对象因 equals() 返回 false,都被存入集合
-
JUnit 断言失败:
assertEquals(p1, p2)底层调用的是 equals(),若没重写,必报错 - Stream.distinct() 失效:去重依赖 equals() + hashCode(),缺一不可
真正要做的不是“避免 ==”,而是明确区分用途
== 并非错误,它有不可替代的用途:
- 快速判空或同一性检查:
if (obj == this) return true;(重写 equals 时第一步) - 性能敏感路径:比如缓存命中判断、状态机跳转,需要确认是否真为同一实例
- 单例、枚举、常量对象比较:它们天然满足“地址即逻辑”
关键在意识:看到 == 作用于引用类型,就要问一句——这里到底想确认“是不是同一个对象”,还是“内容是否一样”?前者用 ==,后者必须靠重写的 equals()。

















