Object.equals() 必须满足等价关系五大公理,违反即破坏集合框架(HashMap/HashSet/ArrayList.contains)行为:
x.equals(x) == truex.equals(y) == y.equals(x)x.equals(y) && y.equals(z) → x.equals(z)x.equals(null) == false@Override
public boolean equals(Object obj) {
// 1. 自引用检查:性能优化
if (this == obj) return true;
// 2. null 检查:满足非空性
if (obj == null) return false;
// 3. 类型检查:getClass vs instanceof 取决于设计意图
// final 类/不可扩展类用 getClass();允许子类对称用 instanceof
if (getClass() != obj.getClass()) return false;
// 4. 转型与字段比较
MyClass other = (MyClass) obj;
return Objects.equals(field1, other.field1) // 空安全比较
&& Double.compare(field2, other.field2) == 0 // 浮点数比较
&& Arrays.equals(arrayField, other.arrayField); // 数组比较
}
// 方案 A:getClass() - 严格类型相等 if (getClass() != obj.getClass()) return false; // 子类实例永远不等于父类实例,保证对称性/传递性 // 适用:值对象、final 类、不期望跨类比较 // 方案 B:instanceof - 允许子类比较 if (!(obj instanceof MyClass)) return false; // 子类可与父类比较,但需所有子类遵守同一比较逻辑 // 风险:违反传递性(A=B, B=C, 但 A≠C) // 适用:接口定义规范、特定框架要求(如 JPA 实体)
Double.compare(d1, d2):正确处理 NaN、-0.0、+0.0Arrays.equals(arr1, arr2):支持多维数组(需 Arrays.deepEquals)Objects.equals(a, b):空安全,内部调用 a.equals(b)若两对象 equals() 相等,则 hashCode() 必须相等
@Override
public int hashCode() {
// Objects.hash() 内部用 31 为质数组合,简洁高效
return Objects.hash(name, salary, hireDay);
}
违反后果:对象作为 HashMap key/HashSet 元素时,无法查找、重复存储、内存泄漏。
class Employee {
String name; double salary; Date hireDay;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Employee other = (Employee) obj;
return Objects.equals(name, other.name)
&& Double.compare(salary, other.salary) == 0
&& Objects.equals(hireDay, other.hireDay);
}
@Override public int hashCode() { return Objects.hash(name, salary, hireDay); }
}
class Manager extends Employee {
double bonus;
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) return false; // 复用父类比较
if (getClass() != obj.getClass()) return false; // 严格类型
Manager other = (Manager) obj;
return Double.compare(bonus, other.bonus) == 0;
}
@Override public int hashCode() { return Objects.hash(super.hashCode(), bonus); }
}
| 陷阱 | 后果 | 修正 |
|---|---|---|
| 只重写 equals 不重写 hashCode | HashMap/Set 失效 | 必须成对重写 |
| equals 参数类型写成 MyClass | 重载而非重写,多态失效 | 参数必须是 Object |
| 比较时用 == 比较引用类型字段 | 内容相等但引用不同返回 false | 用 Objects.equals() |
| 继承层级中 equals 不对称 | 集合行为异常 | 统一用 getClass() 或精心设计 instanceof |
| 可变字段参与 equals/hashCode | 放入集合后修改字段导致找不到 | 仅用不可变字段,或文档声明不可变 |
@Data / @EqualsAndHashCode (Lombok):注解驱动,注意继承处理record (Java 16+):自动生成基于所有组件的 equals/hashCode/toString,不可变,最简洁equals() 不是简单的字段比较,而是领域模型相等性的精确定义。遵循契约、成对重写 hashCode、谨慎处理继承、善用工具类/现代语法,才能写出正确、高效、可维护的相等逻辑。