这是 Lombok 框架的核心注解之一,作用是自动帮你生成 equals() 和 hashCode() 方法。
一、注解核心作用
1. 解决的问题
Java 中如果需要自定义对象的相等性判断,必须手动重写 equals() 和 hashCode() 方法,不仅代码冗长,还容易出错(比如漏字段、逻辑不一致)。
@EqualsAndHashCode 会在编译期自动生成这两个方法,完全遵循 Java 规范:
- **
equals():**按类的字段逐一比较,判断两个对象是否相等 hashCode(): 基于相同字段生成哈希值,保证a.equals(b) == true时a.hashCode() == b.hashCode()
2. 源码分析
从源码可以看到:
java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE) // 源码级注解,仅编译期生效
public @interface EqualsAndHashCode {
// 核心参数
String[] exclude() default {}; // 排除指定字段,不参与比较
String[] of() default {}; // 仅包含指定字段,参与比较
boolean callSuper() default false; // 是否调用父类的equals/hashCode
// ...其他参数
}
@Retention(SOURCE): 仅在源码阶段生效,编译成.class文件后就会被擦除,不影响运行期性能- **
@Target(TYPE):**只能标注在类上
二、代码中 callSuper = true 的关键作用
java
@EqualsAndHashCode(callSuper = true)
public class PTS1400106039In extends PtRequest { ... }
1. 核心含义
- **默认
callSuper = false:**生成的equals()/hashCode()只比较当前类的字段,完全忽略父类 的字段 callSuper = true: 生成的方法会先调用父类的equals()/hashCode(),再比较当前类的字段,保证父类字段也参与相等性判断
2. 注意
如果你的类继承了父类,且父类有自己的业务字段,必须设置 callSuper = true,否则会出现以下的严重逻辑错误:
- 两个子类对象,父类字段不同、子类字段相同,会被错误判定为相等
- 哈希值只基于子类字段生成,导致
HashSet/HashMap存储异常
三、常用参数详解
| 参数 | 作用 | 示例 |
|---|---|---|
callSuper |
是否调用父类方法,继承类必须设为 true |
@EqualsAndHashCode(callSuper = true) |
exclude |
排除指定字段,不参与比较 | @EqualsAndHashCode(exclude = {"id", "createTime"}) |
of |
仅包含指定字段,其他字段全部忽略 | @EqualsAndHashCode(of = {"orderNo", "userId"}) |
doNotUseGetters |
直接访问字段,不调用 getter 方法(默认 false,调用 getter) |
@EqualsAndHashCode(doNotUseGetters = true) |
cacheStrategy |
哈希值缓存策略,LAZY 表示首次计算后缓存 |
@EqualsAndHashCode(cacheStrategy = CacheStrategy.LAZY) |
四、和 @Data 的关系
为什么要同时用 @Data 和 @EqualsAndHashCode(callSuper = true)?这里有一个关键点:
@Data注解默认包含@EqualsAndHashCode,但默认callSuper = false- 当类继承了父类时,
@Data生成的方法会忽略父类字段,必须显式添加@EqualsAndHashCode(callSuper = true)覆盖默认行为!
五、实际效果示例
原始手写代码(冗余易错)↓
java
public class PTS1400106039In extends PtRequest {
private String field1;
private Integer field2;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false; // callSuper = true 才会有这行
PTS1400106039In that = (PTS1400106039In) o;
return Objects.equals(field1, that.field1) && Objects.equals(field2, that.field2);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), field1, field2); // callSuper = true 才会包含父类哈希
}
}
Lombok 注解(一行搞定)↓
java
@Data
@EqualsAndHashCode(callSuper = true)
public class PTS1400106039In extends PtRequest {
private String field1;
private Integer field2;
}
编译后自动生成和上面完全一致的代码,彻底消除冗余。
六、举例说明区别
1. 准备父类和子类
java
import lombok.AllArgsConstructor;
import lombok.Data;
// 父类:包含 id 字段
@Data
@AllArgsConstructor
class BaseEntity {
private Long id;
}
// 子类:包含 name 字段,继承 BaseEntity
@Data
// @EqualsAndHashCode(callSuper = true) // 先注释,测试默认行为
public class User extends BaseEntity {
private String name;
// 构造器:给 id 和 name 赋值
public User(Long id, String name) {
super(id);
this.name = name;
}
}
2. 默认情况测试(不加 callSuper = true)
测试代码:
java
public class Test {
public static void main(String[] args) {
// 两个对象:name相同,id不同
User user1 = new User(1L, "张三");
User user2 = new User(2L, "张三");
System.out.println("user1.equals(user2) = " + user1.equals(user2));
System.out.println("user1.hashCode() = " + user1.hashCode());
System.out.println("user2.hashCode() = " + user2.hashCode());
}
}
运行结果:
user1.equals(user2) = true
user1.hashCode() = 24021040
user2.hashCode() = 24021040
原因:
默认情况下:
- Lombok 只用子类
User的name字段生成equals()和hashCode() - 完全忽略父类
BaseEntity的id字段 - 所以即使
id不同,只要name相同,就判定为相等(错误)
3. 加上注解后测试(不加 callSuper = true)
打开子类注解,再次运行测试代码:
java
@Data
@EqualsAndHashCode(callSuper = true) // 开启父类判断
public class User extends BaseEntity {
运行结果:
java
user1.equals(user2) = false
user1.hashCode() = 24021041
user2.hashCode() = 24021042
原因:
加了 callSuper = true:
- Lombok 会同时使用子类
name+ 父类id生成方法 - 必须所有字段都相同,才判定为相等(正确)
核心区别总结
| 注解方式 | 行为 | 适用场景 |
|---|---|---|
@EqualsAndHashCode(默认) |
只比较当前类字段,忽略父类 | 类没有继承关系,或继承自 Object |
@EqualsAndHashCode(callSuper = true) |
比较当前类 + 父类所有字段 | 有继承关系,父类有业务字段(最常用) |
七、建议
- 继承类必须加
callSuper = true:只要类extends了其他类,就必须显式设置,否则父类字段不参与比较 - 避免和
@Data冲突 :@Data自带@EqualsAndHashCode,如果需要自定义参数,必须显式覆盖 - 谨慎使用
exclude/of:只在明确不需要比较的字段(如 transient、临时字段)时使用,避免漏判 - 和
@ToString保持一致 :如果用了@ToString,建议同步设置@ToString(callSuper = true),保证日志打印完整
八、HashCode计算
Lombok 生成的 hashCode = 所有参与计算的字段 按规则混合算出的数字
先看 Lombok 生成 hashCode 的固定算法
Lombok 统一用这个公式计算 hashCode(简化版):
java
// 伪代码
public int hashCode() {
// 初始值
int result = 1;
// 遍历所有【参与计算的字段】
for (字段 : 参与字段) {
// 用字段值混合计算
result = result * 31 + 字段.hashCode();
}
// 最终返回这个数字
return result;
}
- 正向 :如果
hashCode不相等 → 则 参与字段一定不同。(这是成立的,因为如果相同,hash 必须一样) - 反向 :如果 参与字段不同 →
hashCode不一定不同。(可能碰撞) - 反向特例 :如果 参与字段相同 →
hashCode一定相同。(这是必须遵守的契约)
