首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java equals() 方法完全指南:从契约到最佳实践

     編輯:Java 相關知識

equals() 核心契约:等价关系的数学定义

Object.equals() 必须满足等价关系五大公理,违反即破坏集合框架(HashMap/HashSet/ArrayList.contains)行为:

  1. 自反性:x.equals(x) == true
  2. 对称性:x.equals(y) == y.equals(x)
  3. 传递性:x.equals(y) && y.equals(z) → x.equals(z)
  4. 一致性:多次调用结果不变,除非比较字段被修改
  5. 非空性:x.equals(null) == false

标准实现模板(Effective Java 风格)

@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); // 数组比较
}

关键决策点深度解析

getClass() vs instanceof

// 方案 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.0
  • Arrays.equals(arr1, arr2):支持多维数组(需 Arrays.deepEquals)
  • Objects.equals(a, b):空安全,内部调用 a.equals(b)

equals() 与 hashCode() 不可分割的契约

若两对象 equals() 相等,则 hashCode() 必须相等

@Override
public int hashCode() {
    // Objects.hash() 内部用 31 为质数组合,简洁高效
    return Objects.hash(name, salary, hireDay);
}

违反后果:对象作为 HashMap key/HashSet 元素时,无法查找、重复存储、内存泄漏。

实战案例:Employee 与 Manager 继承层级

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 不重写 hashCodeHashMap/Set 失效必须成对重写
equals 参数类型写成 MyClass重载而非重写,多态失效参数必须是 Object
比较时用 == 比较引用类型字段内容相等但引用不同返回 false用 Objects.equals()
继承层级中 equals 不对称集合行为异常统一用 getClass() 或精心设计 instanceof
可变字段参与 equals/hashCode放入集合后修改字段导致找不到仅用不可变字段,或文档声明不可变

现代工具:IDE 生成 / Lombok / Record

  • IDE Generate → equals()/hashCode():可靠但代码冗长
  • @Data / @EqualsAndHashCode (Lombok):注解驱动,注意继承处理
  • record (Java 16+):自动生成基于所有组件的 equals/hashCode/toString,不可变,最简洁

总结

equals() 不是简单的字段比较,而是领域模型相等性的精确定义。遵循契约、成对重写 hashCode、谨慎处理继承、善用工具类/现代语法,才能写出正确、高效、可维护的相等逻辑。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved