「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。
序
Lombok极大的简化了我们的代码,增强了代码的可读性,同时呢也提供了许多实用的注解方法,像@Builder
,@Getter
,@Setter
,@Data
,@Slf4j
,@ToString
之类的。那我们有了解过lombok的原理吗?有了解过lombok使用过程中存在的问题吗?很多时候,对工具底层原理不了解,会误踩很多的坑,这篇文章就介绍下我在使用lombok过程中遇到过的一些问题。
正文
@EqualsAndHashCode 和 @Data 在对象判断是否相等时的问题
先看看两个有父子继承关系的类
# 父类
import lombok.Data;
@Getter
@Setter
public class Father {
private String fId;
}
# 子类
@Getter
@Setter
@EqualsAndHashCode
public class Sub extends Father{
private String subId;
}
父类和子类我的注解使用的基本一样,子类就多了个@EqualsAndHashCode
注解,排除下受其他注解影响的可能性。
假设,现在我们有两个子对象实例(sub1
,sub2
),它们的subId属性值一样,而父类fId属性值不一样,通过equals方法来比较,结果会是什么呢?
public class Test {
public static void main(String[] args) {
Sub sub1 = new Sub();
sub1.setSubId("子id1");
sub1.setFId("父id1");
Sub sub2 = new Sub();
sub2.setSubId("子id1");
sub2.setFId("父id2");
System.out.println(sub1.equals(sub2));
}
}
我写了一个测试类,代码不用多解释,我直接贴上结果。
应该没有人会说结果对吧?本应该是false
的,现在却成了true
,这是为啥呢?
1.原因分析
我们都知道lombok是在编译期给代码加上get、set方法的,具体原因这里不讲。这是什么意思呢?就是说使用了lombok注解的类,在生成.class文件时该有的东西就全有了。所以分析原因,我们可以看看编译生成的.class文件。比较的是Sub
类的对象实例,看看它的class文件。
public class Sub extends Father {
private String subId;
····
public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Sub)) {
return false;
} else {
Sub other = (Sub)o;
if (!other.canEqual(this)) {
return false;
} else {
1 Object this$subId = this.getSubId();
2 Object other$subId = other.getSubId();
3 if (this$subId == null) {
if (other$subId != null) {
return false;
}
} else if (!this$subId.equals(other$subId)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(final Object other) {
return other instanceof Sub;
}
····
}
我只留下了相关代码,我们主要看下标记了1,2,3的地方,在这块else代码分支里,判断该对象是否相等,全部都是比较的subId
这个属性,完全没有父类属性fId
啥事儿。换言之,判断两个Sub对象实例是否相等,只跟subId
属性有关系。所以,你明白了吗?
标题上还跟@Data
注解有关系啊,那又有啥问题呢?
@Data是一个复合注解,它包含了@EqualsAndHashCode注解,所以你标注@Data注解会存在同样的问题
2.解决方案
@EqualsAndHashCode
注解中提供了callSuper
方法,默认是false,只要手动置为true就可以解决上面的问题了。其作用是在计算此类中的字段之前,调用超类的equals和hashCode实现
,加上之后的变化是啥呢,其实就是在上面标注了1,2,3代码的else分支上加了个else if分支,调用了super的euqals方法,代码贴给你。所以加上后就会牵扯到父类中的equals判断了,如果你父类也重写了equals方法的话,就跟父类的属性值有关系喽。
else if (!super.equals(o)) {
return false;
}
所以到这儿,你就知道了,lombok会遇到的一个“坑”了,后面就会注意了。
@Builder 和 默认构造函数的冲突问题
发现这个问题是由一个报错追踪到的
:com.alibaba.fastjson.JSONException: default constructor not found.
原本我想将一个String对象反序列化回来,代码执行报错,我想啊,默认构造函数,只要你不加带参构造函数,这不是每个类默认都会给吗?而且我也没加啊。
我在这个POJO类上标注了@Getter
,@Setter
,@Builder
,@ToString
几个注解,有了上面的讲解,我相信大家都有直觉去看.class文件,我也一样,去喽了一眼.class文件,发现确实没有默认无参构造,却多了个全部参数都有的构造函数。
1.原因分析
看了看.class文件的代码,发现全参构造是@Builder使用的。那我正常的想法,它没有我就给它加上一个默认无参构造不就好了,我加上了@NoArgsConstructor
,加上后查看class文件,发现还是没有无参数构造,所以我又手动写了个无参构造函数,加上后报其他错误。。。。心态不好了
Error:(11, 1) java: 无法将类 XXXX中的构造器 XXXX应用到给定类型;
需要: 没有参数
找到: java.long.String,java.long.String,java.long.String,java.long.String
原因: 实际参数列表和形式参数列表长度不同
这是很明显需要有参构造而我没有,所以我又只能写了一个有参构造函数,根本原因是@Builder需要使用有参数的构造函数。此时转念一想,我是不是可以不自己写有参和无参构造函数,使用注解呢,所以我加上了@NoArgsConstructor
,@AllArgsConstructor
两个注解。然后去查看class文件,发现该有的无参和全参构造函数都出现了,问题解决。
2.本质原因
究其原因啊,如果你使用了@Builder
注解,而不显式的给出无参和全参数构造函数,@Builder会默认删除掉默认的无参构造函数,自己生成一个全参构造函数,这样再使用JSON进行反序列化时,JSON的方法就会报找不到无参构造函数。
总结
开源给我们带来了很多的便利,但是,我们也必须多去了解开源的底层,这样才能避免踩到“坑”。