【填坑集锦】Lombok相关问题

394 阅读5分钟

「这是我参与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注解,排除下受其他注解影响的可能性。

假设,现在我们有两个子对象实例(sub1sub2),它们的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));
    }
}

我写了一个测试类,代码不用多解释,我直接贴上结果。

image.png

应该没有人会说结果对吧?本应该是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的方法就会报找不到无参构造函数。

总结

开源给我们带来了很多的便利,但是,我们也必须多去了解开源的底层,这样才能避免踩到“坑”。

求波关注公众号:java精进天路