在认识 @EqualsAndHashCode 这个注解之前 , 我么首先需要知道 Lombok ,
Lombok项目是一个Java库,它会自动插入编辑器和构建工具中,Lombok提供了一组有用的注释,用来消除Java类中的大量样板代码。仅五个字符(@Data)就可以替换数百行代码从而产生干净,简洁且易于维护的Java类。
Lombok 常用注解:
@Setter :注解在类或字段,注解在类时为所有字段生成setter方法,注解在字段上时只为该字段生成setter方法。
@Getter :使用方法同上,区别在于生成的是getter方法。
@ToString :注解在类,添加toString方法。
@EqualsAndHashCode: 注解在类,生成hashCode和equals方法。
@NoArgsConstructor: 注解在类,生成无参的构造方法。
@RequiredArgsConstructor: 注解在类,为类中需要特殊处理的字段生成构造方法,比如final和被@NonNull注解的字段。
@AllArgsConstructor: 注解在类,生成包含类中所有字段的构造方法。
@Data: 注解在类,生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
@Slf4j: 注解在类,生成log变量,严格意义来说是常量。
在上面我们阐述过了 Lombok 的@Date 的好处 , 以及用法 , @Data 帮助我们省略了@Setter,@Getter,@ToString等注解,一般对于普通的实体类使用该注解,不会出现什么问题,但是当我们把这个注解,使用在派生类上(子类),就会出现一个小问题。
1 . 首先我们定义一个父类 Father01.java
import lombok.Data;
/**
* @description: 自定义父类
* @author: huang
* @create: 2020-07-22 19:42
**/
@Data
public class Father01 {
private int name;
private String code;
}
2 . 再定义一个子类 , 去继承 Father01.java , 且让 Child01.java 拥有自己的属性 , 也使用@Data 注解该类
import lombok.Data;
/**
* @description: 子类
* @author: huang
* @create: 2020-07-22 20:02
**/
@Data
public class Child01 extends Father01 {
private String address;
private Long crd;
public Father01(String name, String code) {
this.name = name;
this.code = code;
}
}
3 . 此时 IDEA 或者 Eclipse 开发工具 , 会在子类的 @Data 注解上进行警告信息提示 , "Annotate class 'Child01' as @EqualsAndHashCode"
4 . 按照提示 加上@EqualsAndHashCode 注解后 , 'Child01' 类就变成了这个样子
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @description: 子类
* @author: huang
* @create: 2020-07-22 20:02
**/
@EqualsAndHashCode(callSuper = true)
@Data
public class Child01 extends Father01 {
private String address;
private Long crd;
public Child01(String address, long crd, String name, String code) {
super(name,code);
this.address = address;
this.crd = crd;
}
}
5 . 点进去源码 , 源码对该注解的解释 (不敢恭维) , 笔者感觉官方写的有些模糊.......
总的来说 这个注解 @EqualsAndHashCode(callSuper = true) 的作用就是自动的给model bean实现equals方法和hashcode方法。
至于为什么 "callSuper = true" , 下面笔者就针对 "callSuper = true" 和 "callSuper = false" 的两种不同情况实验下 我们还将 "callSuper" 的值 改为 false
首先我们来看下 父类编译后的 equals方法 和 hashcode , 里面均出现了两个属性 , 一个是code , 一个 name (笔者已标记出来)
public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Father01)) {
return false;
} else {
Father01 other = (Father01)o;
if (!other.canEqual(this)) {
return false;
} else if (this.getName() != other.getName()) { // name属性
return false;
} else {
Object this$code = this.getCode(); // code 属性
Object other$code = other.getCode();
if (this$code == null) {
if (other$code != null) {
return false;
}
} else if (!this$code.equals(other$code)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(final Object other) {
return other instanceof Father01;
}
public int hashCode() {
int PRIME = true;
int result = 1;
int result = result * 59 + this.getName(); // neme 属性
Object $code = this.getCode(); // code 属性
result = result * 59 + ($code == null ? 43 : $code.hashCode());
return result;
}
然后我们来看 子类的 编译后类文件 , 从继承角度讲 Father01 有的属性,Child01 也是有的。请注意编译后的文件
请注意: hashCode 方法里有一句话 result = 1
public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Child01)) {
return false;
} else {
Child01 other = (Child01)o;
if (!other.canEqual(this)) {
return false;
} else {
Object this$address = this.getAddress(); // address 属性
Object other$address = other.getAddress();
if (this$address == null) {
if (other$address != null) {
return false;
}
} else if (!this$address.equals(other$address)) {
return false;
}
Object this$crd = this.getCrd(); // crd 属性
Object other$crd = other.getCrd();
if (this$crd == null) {
if (other$crd != null) {
return false;
}
} else if (!this$crd.equals(other$crd)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(final Object other) {
return other instanceof Child01;
}
public int hashCode() {
int PRIME = true;
int result = 1; // 注意这里 result = 1
Object $address = this.getAddress(); // address 属性
int result = result * 59 + ($address == null ? 43 : $address.hashCode());
Object $crd = this.getCrd(); // crd 属性
result = result * 59 + ($crd == null ? 43 : $crd.hashCode());
return result;
}
这样问题就很明显了 , 当子类 继承 父类时 , 虽然等于拥有了 父类的相关属性 , 但是 equals 和 hashCode 方法在进行比较时候 , 并没有对父类的相关属性纳入比较范围 ,就会出现以下问题 :
是不是很意外 ? 这个因为在子类 Child01 里面使用@EqualsAndHashCode(callSuper = false) ,所以在进行
child01.equals(child02)
时并不调用父类的属性,那么子类属性里面的相同的话,那hashcode的值就相同啦,所以代码里面的2个 Child01 的equals方法的返回值是true
然后为了验证结果正确性 , 再将 @EqualsAndHashCode(callSuper = false) 改为 @EqualsAndHashCode(callSuper = true)
public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Child01)) {
return false;
} else {
Child01 other = (Child01)o;
if (!other.canEqual(this)) {
return false;
} else if (!super.equals(o)) {
return false;
} else {
Object this$address = this.getAddress();
Object other$address = other.getAddress();
if (this$address == null) {
if (other$address != null) {
return false;
}
} else if (!this$address.equals(other$address)) {
return false;
}
Object this$crd = this.getCrd();
Object other$crd = other.getCrd();
if (this$crd == null) {
if (other$crd != null) {
return false;
}
} else if (!this$crd.equals(other$crd)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(final Object other) {
return other instanceof Child01;
}
public int hashCode() {
int PRIME = true;
int result = super.hashCode();
// 注意这里 在 @EaualsAndHashCode(... false)时候这里并未调用父类 的hashCode() ,
// 当 @EqualsAndHashCode(... true) 时候 这里编译后的类 在hashcode 之前调用了 父类的hashCode() ,
// 也就是在比较两个子类之前 , 优先比较父类
Object $address = this.getAddress();
result = result * 59 + ($address == null ? 43 : $address.hashCode());
Object $crd = this.getCrd();
result = result * 59 + ($crd == null ? 43 : $crd.hashCode());
return result;
}
再次运行 , 查看结果 , 如下
至此就是 有关于 Lombok 在 重写 equals 和 hashCode 时候 会出现的问题 以及解决办法