1. 概述
本文将深入探讨 Lombok 的 @EqualsAndHashCode
注解,该注解能基于类的字段自动生成 equals()
和 hashCode()
方法。对于有经验的 Java 开发者来说,手动实现这两个方法既繁琐又容易出错,Lombok 提供了这个注解来简化开发流程。
2. @EqualsAndHashCode 基本用法
@EqualsAndHashCode
注解会默认使用所有非静态、非瞬态字段生成 equals()
和 hashCode()
方法。如果需要自定义字段范围,可以通过以下方式控制:
- 使用
@EqualsAndHashCode.Include
显式包含字段 - 使用
@EqualsAndHashCode.Exclude
显式排除字段
来看个简单例子:
@EqualsAndHashCode
public class Employee {
private String name;
private int id;
private int age;
}
编译后 Lombok 会自动生成以下方法(反编译代码):
public class EmployeeDelomboked {
private String name;
private int id;
private int age;
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Employee)) return false;
Employee other = (Employee) o;
if (!other.canEqual(this)) return false;
if (this.getId() != other.getId()) return false;
if (this.getAge() != other.getAge()) return false;
Object this$name = this.getName();
Object other$name = other.getName();
if (this$name == null) {
if (other$name == null) return true;
} else if (this$name.equals(other$name)) {
return true;
}
return false;
}
protected boolean canEqual(Object other) {
return other instanceof Employee;
}
public int hashCode() {
final int PRIME = 59;
int result = 1;
result = result * PRIME + this.getId();
result = result * PRIME + this.getAge();
Object $name = this.getName();
result = result * PRIME + ($name == null ? 43 : $name.hashCode());
return result;
}
}
⚠️ 注意:Lombok 在生成方法时会额外创建 canEqual()
方法,用于确保比较对象是当前类的实例,避免子类破坏 equals 约定。
3. 排除特定字段
3.1. 字段级别排除:@EqualsAndHashCode.Exclude
在字段上直接使用 @EqualsAndHashCode.Exclude
注解,可将其从生成逻辑中排除:
@EqualsAndHashCode
public class Employee {
private String name;
@EqualsAndHashCode.Exclude
private int id;
@EqualsAndHashCode.Exclude
private int age;
}
生成的 hashCode()
方法将只使用 name
字段:
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $name = this.getName();
result = result * PRIME + ($name == null ? 43 : $name.hashCode());
return result;
}
3.2. 类级别排除
在类注解中通过 exclude
属性指定要排除的字段:
@EqualsAndHashCode(exclude = {"id", "age"})
public class Employee {
private String name;
private int id;
private int age;
}
效果与字段级排除完全相同,但更集中管理。
4. 包含特定字段
4.1. 字段级别包含:@EqualsAndHashCode.Include
需要显式指定包含字段时,配合 onlyExplicitlyIncluded = true
使用:
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Employee {
@EqualsAndHashCode.Include
private String name;
@EqualsAndHashCode.Include
private int id;
private int age; // 未标注,自动排除
}
✅ 只有被 @EqualsAndHashCode.Include
标注的字段会参与计算。
4.2. 方法级别包含
可以将方法返回值纳入计算逻辑:
@EqualsAndHashCode
public class Employee {
private String name;
private int id;
private int age;
@EqualsAndHashCode.Include
public boolean hasOddId() {
return id % 2 != 0;
}
}
生成的 hashCode()
会包含方法结果:
public int hashCode() {
final int PRIME = 59;
int result = 1;
result = result * PRIME + this.getId();
result = result * PRIME + this.getAge();
result = result * PRIME + (this.hasOddId() ? 79 : 97); // 方法结果参与计算
final Object $name = this.getName();
result = result * PRIME + ($name == null ? 43 : $name.hashCode());
return result;
}
4.3. 类级别包含
使用 of
属性指定包含字段(不推荐,未来可能废弃):
@EqualsAndHashCode(of = {"name", "id"})
public class Employee {
private String name;
private int id;
private int age;
}
⚠️ 踩坑提示:Lombok 官方推荐使用 onlyExplicitlyIncluded
替代 of
,后者未来可能被废弃。
5. 继承关系处理
默认情况下,子类的 @EqualsAndHashCode
**不会调用父类的 equals()
和 hashCode()
**。要包含父类逻辑,需设置 callSuper = true
:
@EqualsAndHashCode(callSuper = true)
public class Manager extends Employee {
private String departmentName;
private int uid;
}
生成的 hashCode()
会调用父类方法:
public int hashCode() {
final int PRIME = 59;
int result = super.hashCode(); // 调用父类方法
result = result * PRIME + this.getUid();
final Object $departmentName = this.getDepartmentName();
result = result * PRIME + ($departmentName == null ? 43 : $departmentName.hashCode());
return result;
}
6. 全局配置
在项目根目录创建 lombok.config
文件可配置全局行为:
6.1. lombok.equalsAndHashCode.doNotUseGetters
lombok.equalsAndHashCode.doNotUseGetters = [true | false] (默认: false)
true
:直接访问字段(即使存在 getter)false
:优先使用 getter
6.2. lombok.equalsAndHashCode.callSuper
lombok.equalsAndHashCode.callSuper = [call | skip | warn] (默认: warn)
call
:子类自动调用父类方法skip
:不调用父类方法warn
:不调用但发出警告(默认行为)
6.3. lombok.equalsAndHashCode.flagUsage
lombok.equalsAndHashCode.flagUsage = [warning | error] (默认: 未设置)
设置为 warning
或 error
后,使用 @EqualsAndHashCode
会触发相应级别的提示。
7. 优缺点分析
7.1. 优势
✅ 减少样板代码:自动生成方法,避免手动编写
✅ 高度可定制:灵活控制字段包含/排除
✅ 降低出错风险:避免手动实现时的常见错误(如忘记更新 hashCode()
)
7.2. 劣势
❌ 哈希冲突风险:不同字段值可能产生相同哈希值 ❌ 框架兼容问题:某些依赖反射的框架可能无法识别生成的方法 ❌ 可读性下降:隐藏了方法实现细节,增加代码理解成本
8. 常见问题
双向关联导致的栈溢出
当类之间存在双向关联时,可能触发 StackOverflowError
:
@EqualsAndHashCode
public class Employee {
private String name;
private Manager manager; // 关联 Manager
}
@EqualsAndHashCode
public class Manager {
private String name;
private Employee assistantManager; // 关联 Employee
}
问题原因:
Employee.hashCode()
→Manager.hashCode()
→Employee.hashCode()
→ 无限循环
解决方案: 排除其中一个关联字段:
@EqualsAndHashCode(exclude = "manager")
public class EmployeeV2 {
private String name;
private Manager manager;
}
9. 总结
Lombok 的 @EqualsAndHashCode
注解通过自动生成 equals()
和 hashCode()
方法,显著简化了开发工作。主要要点:
- 默认使用所有非静态/非瞬态字段
- 灵活的字段控制:通过
Include
/Exclude
精准定制 - 继承处理:使用
callSuper=true
包含父类逻辑 - 全局配置:通过
lombok.config
统一管理行为 - 避坑指南:双向关联时需排除字段避免栈溢出
虽然该注解能提升开发效率,但在复杂继承结构或需要特殊哈希算法的场景下,仍需谨慎评估其适用性。完整示例代码可在 GitHub 查看。