必须重写equals和hashCode以保证逻辑相等对象在HashMap等集合中行为一致:若equals为true则hashCode必相同;二者需遵守自反性、对称性、传递性、一致性及null处理约定,且字段选择须合理。

在Java中重写 equals 和 hashCode,核心原因只有一个:**保证对象逻辑相等时,行为一致且能正确工作于基于哈希的集合(如 HashMap、HashSet)中**。不重写,或重写不合规,会导致“明明两个对象内容一样,却查不到”“同一个对象存了两份”等诡异问题。
equals 与 hashCode 必须保持一致性
这是最根本的设计契约:如果两个对象通过 equals 判断为 true,那么它们的 hashCode 值必须相同;反之则不要求(不同对象可以有相同哈希值,即哈希碰撞)。JDK 的集合类(如 HashMap)正是依赖这一约定工作的:先用 hashCode 快速定位桶位置,再用 equals 精确比对键值。
- 只重写
equals不重写hashCode→ 逻辑相等的对象可能被散列到不同桶,get()或contains()失败 - 只重写
hashCode不重写equals→ 即使哈希值相同,equals默认比较引用,仍判为不等,集合操作仍出错 - 两者都重写但逻辑不一致(例如
equals比较 name+age,hashCode只用 name 计算)→ 违反契约,行为不可预测
重写 equals 要遵守五项基本约定
equals 方法不是随便写的,它必须满足自反性、对称性、传递性、一致性,以及对 null 的处理。违反任一约定,可能引发 HashSet 重复、TreeSet 异常、甚至并发场景下死循环等严重问题。
-
自反性:对任意非 null 引用
x,x.equals(x)必须返回true -
对称性:若
x.equals(y)为true,则y.equals(x)也必须为true -
传递性:若
x.equals(y)且y.equals(z)为true,则x.equals(z)必须为true - 一致性:多次调用结果不变(前提是没有修改影响比较的字段)
-
对 null 的处理:对任意非 null 引用
x,x.equals(null)必须返回false
实践中,推荐使用 Objects.equals(a, b) 安全比较字段,避免空指针;用 instanceof + 类型强转做类型检查,而非 getClass() == obj.getClass()(除非明确要求严格类型限制)。
立即学习“Java免费学习笔记(深入)”;
hashCode 的实现要兼顾分布性与确定性
hashCode 不必唯一,但应尽量让逻辑不同的对象产生不同哈希值(减少碰撞),更重要的是:只要参与比较的字段没变,多次调用必须返回相同值。常见写法是使用 Objects.hash(field1, field2, ...),它自动处理 null 并组合字段哈希。
- 不要在
hashCode中使用可变字段(如普通 setter 修改的属性),否则对象加入HashSet后再修改字段,就再也找不到了 - 不要用随机数、当前时间、内存地址等不确定值参与计算
- 若类是不可变的(如
String、自定义的Point),用所有关键字段参与哈希计算最稳妥
IDE 和 Lombok 可以帮你生成,但得懂原理
IntelliJ 或 Eclipse 都支持自动生成 equals 和 hashCode 方法,Lombok 的 @EqualsAndHashCode 更是只需一行注解。但生成只是起点——你仍需确认:选了哪些字段?是否包含父类字段?是否忽略某些业务上不该参与比较的字段(如数据库主键 ID、创建时间)?
- 默认生成通常包含所有非静态字段,可能过度(比如含临时缓存字段)
- 若继承自某个父类,且父类已重写
equals/hashCode,子类生成时应调用super.equals()和super.hashCode() - Lombok 的
callSuper = true或exclude = {"id"}等参数要按需配置
不复杂但容易忽略。










