是的。equals()不重写时默认用==比较内存地址,导致逻辑相等的对象被视为不等;必须同时重写hashCode()以保证哈希集合正确查找,且二者参与比较的字段须严格一致。

equals() 方法不重写就等于 == 吗?
是的。Java 中所有类默认继承自 Object,其 equals() 方法底层就是用 == 比较两个引用是否指向同一内存地址。这意味着:即使两个 Person 对象字段值完全相同,只要不是同一个实例,equals() 就返回 false。
常见错误现象:
- 往
HashSet或HashMap里添加自定义对象后查不到 —— 因为没重写equals()和hashCode() -
list.contains(new Person("Alice", 25))总是返回false,哪怕列表里已有相同字段的对象
实操建议:
- 只要类会被用于集合查找、去重、作为
Map键,就必须重写equals() - 重写时只比较业务上“逻辑相等”的字段(比如
id或username),不要包含可变字段(如lastLoginTime) - 务必先用
==判断是否为同一引用,再判null,最后用Objects.equals()比较字段 —— 避免 NPE
为什么重写了 equals() 还必须重写 hashCode()?
因为哈希集合(HashSet、HashMap 的 key)依赖 hashCode() 快速定位桶位置。如果两个对象 equals() 返回 true,但 hashCode() 不同,它们会被散列到不同桶中,导致“明明存在却查不到”。
立即学习“Java免费学习笔记(深入)”;
违反约定的后果:
-
HashSet.add(obj)可能重复添加逻辑相等的对象 -
map.get(key)返回null,即使该key已存在 - JVM 不报错,但行为不可预测 —— 这是最难调试的一类 bug
实操建议:
- 用
Objects.hash(field1, field2, ...)生成hashCode(),它自动处理null - 参与
hashCode()计算的字段,必须和equals()中比较的字段严格一致 - 避免在
hashCode()中使用可变字段(如普通 setter 修改的属性),否则对象加入集合后修改字段会导致哈希码变化,再也找不回来
IDE 自动生成的 equals/hashCode 安全吗?
IntelliJ / Eclipse 生成的代码基本可用,但有三个关键点容易被忽略:
- 默认会把所有非静态字段都纳入比较和哈希计算 —— 如果类里有
transient字段、缓存字段或懒加载代理(如 Hibernate 的LazyInitializationException相关字段),必须手动剔除 - 若字段是集合(如
List),生成的Objects.equals()调用是安全的;但若字段是自定义对象,且该对象没重写equals(),整个链路仍会失效 - 生成的
hashCode()使用Objects.hash()是对的,但要注意:如果字段本身重写了hashCode(),要确认其实现是否稳定(比如是否依赖运行时状态)
示例:一个典型的安全重写片段
public class User {
private final String username;
private final int age;
private transient String cacheToken; // 不参与 equals/hashCode
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age && Objects.equals(username, user.username);
}
@Override
public int hashCode() {
return Objects.hash(username, age); // 不含 cacheToken
}
}
String、Integer 等包装类为什么可以直接用?
因为 JDK 已经为它们正确实现了 equals() 和 hashCode():比较的是值而非引用,且哈希值由值唯一确定。但要注意边界情况:
-
new String("abc").equals("abc")是true,但new String("abc") == "abc"是false -
Integer在 [-128, 127] 范围内会缓存,所以Integer.valueOf(100) == Integer.valueOf(100)成立;超出范围则不一定 - 永远别用
==比较两个Integer是否“值相等”,必须用.equals()
复杂点在于:一旦你组合这些类型(比如 Map),只要嵌套结构里的每一层都满足契约,整个集合的行为就是可预期的。最容易被忽略的是——你自己写的类,哪怕只作为某个多层嵌套对象中的一个字段,也必须守规矩。










