equals 和 hashCode 必须基于完全相同的字段、用一致逻辑计算,且全程规避运行时不确定性;必须用同一组字段参与判断和计算,顺序、数量、类型均需严格一致,优先使用 Objects.equals 和 Objects.hash 统一处理 null、基本类型、数组等边界情况,避免可变字段和 instanceof 导致的契约破坏。

关键在于让 equals 和 hashCode 基于完全相同的字段、用一致的逻辑计算,且全程规避运行时不确定性。不一致不是“可能出错”,而是只要放进 HashSet 或 HashMap 就必然失效。
必须用同一组字段参与判断和计算
哪些字段出现在 equals 的比较逻辑里,就必须原样、原序出现在 Objects.hash(...) 中。多一个、少一个、顺序颠倒,都会破坏契约。
- 比如
equals只比id和name,那hashCode就不能加createdAt,也不能漏掉name - 字段类型要匹配:
int直接用,String用Objects.equals和Objects.hash,别调name.hashCode()(会空指针) - 数组要用
Arrays.hashCode(arr),不能直接传给Objects.hash;浮点数用Float.floatToIntBits(f),避免-0.0和0.0哈希不同
用 Objects 工具类统一处理边界情况
手写哈希算法容易忽略 null、类型转换、精度截断等问题。JDK 7+ 提供的 Objects 是经过验证的安全方案。
-
equals中所有字段比较都走Objects.equals(a, b),自动判空,不抛 NPE -
hashCode全部交给Objects.hash(f1, f2, f3),它内部已处理null、基本类型包装、数组展开等细节 - 不用自己乘质数、异或、位移——这些操作既难维护,又容易因字段顺序或类型差异引入不一致
确保对象状态稳定,避免可变字段干扰
一旦对象被放入 HashSet 或作为 HashMap 的 key,它的 hashCode 就不能变。否则桶位置错乱,对象“消失”。
立即学习“Java免费学习笔记(深入)”;
- 只对
final字段或构造后不再修改的字段参与equals/hashCode - 避免使用
List、Map等可变集合字段——内容变了,哈希值就变,但集合内部不会重新散列 - 绝对不要在
hashCode里用new Date().getTime()、Math.random()或System.identityHashCode(this)
类型检查必须用 getClass(),不能用 instanceof
用 instanceof 容易在继承场景下破坏对称性:子类重写 equals 后,父类实例可能认为子类实例相等,但子类实例不认父类实例。
- 正确写法:
if (obj == null || getClass() != obj.getClass()) return false; - 这样保证了只有同类实例才进入字段比较,也间接确保了
hashCode计算逻辑的范围严格对齐 - 配合
Objects.hash,天然屏蔽了子类字段干扰——因为子类调用父类hashCode时,不会把自身新增字段算进去


















