lombok使用可能遇到的坑
参考链接:https://www.bianxiaofeng.com/article/78
无意间逛博客发现一个以前在项目中看到的注解,当时还不理解为什要加@EqualsAndHashCode(callSuper = true),正好写个例子记录一下。
常用注解
在日常使用中会经常用到以下注解:
- @Data: 用在类上,用于生成setter/getter/equals/canEqual/hashCode/toString 方法,如果字段为
final
属性则不会为它生成get/set方法.
- @Setter 用在类或字段,注解在类时为所有字段生成setter方法,注解在字段上时只为该字段生成setter方法。
- @Getter 用在类或字段,注解在类时为所有字段生成getter方法,注解在字段上时只为该字段生成getter方法。
- @ToString 用在类上,用于生成toString方法。
- @EqualsAndHashCode 用在类上,生成hashCode和equals方法。
- @NoArgsConstructor 用在类上,生成无参的构造方法。
- @RequiredArgsConstructor 用在类上,为类中需要特殊处理的字段生成构造方法,比如final和被@NonNull注解的字段。
- @AllArgsConstructor 用在类上,生成包含类中所有字段的构造方法。
- @Slf4j 用在类上,生成log变量,严格意义来说是常量。
private static final Logger log = LoggerFactory.getLogger(UserController.class);
- 平常我使用的时候只是在实体类上加一个@Data,它包括了setter、getter、toString、EqualsAndHashCode等。
工作原理:
在使用Javac进行编译的过程中会先对源代码进行分析,生成一个抽象语法树(AST),然后调用实现了”JSR 269 API”的Lombok程序,接着之前生成的抽象语法树进行处理,在使用了@Data等注解的类的语法树上进行修改,追加相应的get/set方法的对应数节点.然后使用修改后的抽象语法树生成字节码.
@EqualsAndHashCode的坑:
举个例子,编写一个父类和一个继承它的子类。
测试父类:
1 2 3 4 5 6 7 8 9
| @Data @NoArgsConstructor @AllArgsConstructor public class TestSuper {
private Integer id;
private String name; }
|
测试子类:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Data public class AppleTestSuper extends TestSuper{
private String code;
private Integer age;
public AppleTestSuper(Integer id,String name,String code,Integer age){ super(id,name); this.code=code; this.age=age; } }
|
main函数:
1 2 3 4 5 6 7 8 9
| public class TestMain {
public static void main(String[] args) { AppleTestSuper appleTestSuper = new AppleTestSuper(1,"ccc","321",11); AppleTestSuper appleTestSuper1 = new AppleTestSuper(2,"ccc","321",11);
System.out.println(appleTestSuper.equals(appleTestSuper1)); } }
|
运行结果:
true
很明显不相等的两个类为什么equals返回为true,正好学习了一下如何反编译class文件,IDEA编译器自带了反编译的工具,可以在setting里的plugins里搜索byte看到下图
或者下载一个JD-GUI,也叫Java Decompiler,直接打开class文件就能反编译成java文件了
反编译后的 equals 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public boolean equals(final Object o) { if (o == this) { return true; } else if (!(o instanceof AppleTestSuper)) { return false; } else { AppleTestSuper other = (AppleTestSuper)o; if (!other.canEqual(this)) { return false; } else { Object this$code = this.getCode(); Object other$code = other.getCode(); if (this$code == null) { if (other$code != null) { return false; } } else if (!this$code.equals(other$code)) { return false; }
Object this$age = this.getAge(); Object other$age = other.getAge(); if (this$age == null) { if (other$age != null) { return false; } } else if (!this$age.equals(other$age)) { return false; }
return true; } } }
|
可以看到,这里只比较了子类中的两个属性,并未比较父类中的属性,所以我们会发生上面的错误
Lombok有@EqualsAndHashCode注解,而@Data这一个注解就包括了@EqualAndHashCode,而@EqualAndHashCode这个注解中有一个callSuper
属性,这个属性就是用于判断是否需要在子类的equals方法中加入父类字段的判断,而该属性的默认值为false.
所以在AppleComputer类上添加@EqualsAndHashCode(callSuper = true)
后再运行下main方法,结果为false.
再次反编译后的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public boolean equals(final Object o) { if (o == this) { return true; } else if (!(o instanceof AppleTestSuper)) { return false; } else { AppleTestSuper other = (AppleTestSuper)o; if (!other.canEqual(this)) { return false; } else if (!super.equals(o)) { return false; } else { Object this$code = this.getCode(); Object other$code = other.getCode(); if (this$code == null) { if (other$code != null) { return false; } } else if (!this$code.equals(other$code)) { return false; }
Object this$age = this.getAge(); Object other$age = other.getAge(); if (this$age == null) { if (other$age != null) { return false; } } else if (!this$age.equals(other$age)) { return false; }
return true; } } }
|
可以看到多了一句 if-else 的判断父类的语句,这样就正常了。
总结
在继承父类的子类中我们不仅要加@Data,同时也要@EqualsAndHashCode(callSuper = true),这样做就完全o鸡儿k了。