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