关于Lombok使用的一些问题

Lombok 是一款非常实用的工具,通过注解能消除冗长的 Java 代码,工程中需要导入 lombok.jar,相应 IDE 的安装方式可参考 官网。项目中使用到的注解主要是 @Data 和 @Builder,前一个注解用在类上,相当于同时使用了 @ToString、@EqualsAndHashCode、@Getter、@Setter 和 @RequiredArgsConstrutor;后一个主要用在类、构造器、方法上,能非常方便的实现建造者模式、链式编程,相关文档可参考 这里

@Builder 注解

在有继承(Inheritance)关系的实体类中使用 @Builder 时,遇到子类的 Builder 对象无法使用到父类的属性,以下方例子来说明

如果是使用 Parent p = new Parent(); p.setParentAge(123);... 方式赋值,那么以下内容可忽略!

1
2
3
4
5
6
7
8
9
10
11
12
13
@Data
//@Builder
public class Parent {
private String parentName;
private int parentAge;
}

@Data
@Builder
public class Child extends Parent {
private String childName;
private int childAge;
}

在使用 Child.builder().parentAge(18).parentName("zhangsan") 时编译失败,查看 Child 对应的 class 文件如下

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
37
38
39
public class Child extends Parent {
private String childName;
private int childAge;

Child(String childName, int childAge) {
this.childName = childName;
this.childAge = childAge;
}

public static Child.ChildBuilder builder() {
return new Child.ChildBuilder();
}

public static class ChildBuilder {
private String childName;
private int childAge;

ChildBuilder() {
}

public Child.ChildBuilder childName(String childName) {
this.childName = childName;
return this;
}

public Child.ChildBuilder childAge(int childAge) {
this.childAge = childAge;
return this;
}

public Child build() {
return new Child(this.childName, this.childAge);
}

public String toString() {
return "Child.ChildBuilder(childName=" + this.childName + ", childAge=" + this.childAge + ")";
}
}
}

并未发现有父类的属性存在。若是在父类中加 @Builder 注解而子类不加,则会报如下错误

1
2
3
4
Error:(14, 8) java: 无法将类 com.leaface.Parent中的构造器 Paren t应用到给定类型;
需要: java.lang.String,int
找到: 没有参数
原因: 实际参数列表和形式参数列表长度不同

之后参考了有关文档,将代码改成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Parent {
private final String parentName;
private final int parentAge;
}

@Data
public class Child extends Parent {
private final String childName;
private final int childAge;

@Builder
public Child(String parentName, int parentAge, String childName, int childAge) {
super(parentName, parentAge);
this.childName = childName;
this.childAge = childAge;
}
}

PS:父类中的 @NoArgsConstructor 是在使用了 @Data 注解后,为解决子类出现 Lombok needs a default constructor in the base class 这个错误而加的

这样配置后,子类就能设置父类的属性了,注意:此时父类是没有加 @Builder 的,而我们项目中又需要这个注解,若是都加上 @Builder,子类编译报错

1
2
Error:(17, 5) java: com.leaface.Child 中的 builder() 无法隐藏 com.leaface.Parent 中的 builder()
返回类型com.leaface.Child.ChildBuilder与com.leaface.Parent.ParentBuilder不兼容

以下是 @Builder 注解源码

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
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.SOURCE)
public @interface Builder {
String builderMethodName() default "builder";

String buildMethodName() default "build";

String builderClassName() default "";

boolean toBuilder() default false;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Default {
}

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.SOURCE)
public @interface ObtainVia {
String field() default "";

String method() default "";

boolean isStatic() default false;
}
}

其中的 builderMethodName 默认为 builder,子类试图公开和父类具有相同名称的构建器,所以报错。

解决方案:为父类和子类分别指定不同名称的构建器!!!

例如在子类中加 builderMethodName = "leafChildBuilder",修改后代码如下

1
2
3
4
5
6
7
8
9
10
11
12
@Data
public class Child extends Parent{
private String childName;
private int childAge;

@Builder(builderMethodName = "leafChildBuilder")
public Child(String parentName, int parentAge, String childName, int childAge) {
super(parentName, parentAge);
this.childName = childName;
this.childAge = childAge;
}
}

查看子类的 class 文件,发现已生成其对应的构建器 leafChildBuilder() 和父类的属性

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
37
38
39
40
41
42
43
public static Child.ChildBuilder leafChildBuilder() {
return new Child.ChildBuilder();
}

public Child(String parentName, int parentAge, String childName, int childAge) {
super(parentName, parentAge);
this.childName = childName;
this.childAge = childAge;
}

public static class ChildBuilder {
private String parentName;
private int parentAge;
private String childName;
private int childAge;

ChildBuilder() {
}

public Child.ChildBuilder parentName(String parentName) {
this.parentName = parentName;
return this;
}

public Child.ChildBuilder parentAge(int parentAge) {
this.parentAge = parentAge;
return this;
}

public Child.ChildBuilder childName(String childName) {
this.childName = childName;
return this;
}

public Child.ChildBuilder childAge(int childAge) {
this.childAge = childAge;
return this;
}

public Child build() {
return new Child(this.parentName, this.parentAge, this.childName, this.childAge);
}
}

这里没有改父类的构建器名称,所以还是默认的 builder(),两者已不存在冲突

1
2
3
public static Parent.ParentBuilder builder() {
return new Parent.ParentBuilder();
}

使用时,子类直接调 leafChildBuilder() 即可,父类还是使用默认的 builder()

1
2
3
Child child = Child.leafChildBuilder().parentAge(88).childAge(18).childName("zhangsan").build();

Parent parent = Parent.builder().parentName("admin").parentAge(99).build();

@SuperBuilder 注解(推荐)

Lombok v1.18.2 推出了 @SuperBuilder 注解,这样就不用再定义特殊的构建器了,比如这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Data
@NoArgsConstructor
@SuperBuilder
public class Parent {
private String parentName;
private int parentAge;
}

@Data
@SuperBuilder
public class Child extends Parent {
private String childName;
private int childAge;
}

使用默认的 builder() 调用即可

1
2
3
Child child = Child.builder().parentAge(88).childAge(18).childName("zhangsan").build();

Parent parent = Parent.builder().parentName("admin").parentAge(99).build();

以上两种方式,赋值后均能通过 @Data 提供的 get 方法取到值。

@SuperBuilder 和 @Builder 不能在同一继承体系中混用,否则会编译失败。多层级继承中这两者的使用方式同上!

至此,Lombok @Builder 继承问题解决!

附录

最后分别附上使用了 @Builder 和 @SuperBuilder 后的父类、子类 class 文件(省略了 @Data 注解)

@Builder class

Parent.class ======>>>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Parent {
private String parentName;
private int parentAge;

public static Parent.ParentBuilder builder() { return new Parent.ParentBuilder();}

public Parent(String parentName, int parentAge) { this.parentName = parentName; this.parentAge = parentAge;}

public static class ParentBuilder {
private String parentName;
private int parentAge;

ParentBuilder() {}

public Parent.ParentBuilder parentName(String parentName) { this.parentName = parentName; return this; }

public Parent.ParentBuilder parentAge(int parentAge) { this.parentAge = parentAge; return this; }

public Parent build() { return new Parent(this.parentName, this.parentAge); }

public String toString() {
return "Parent.ParentBuilder(parentName=" + this.parentName + ", parentAge=" + this.parentAge + ")"; }
}
}

Child.class ======>>>

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 class Child extends Parent {
private String childName;
private int childAge;

public Child(String parentName, int parentAge, String childName, int childAge) {
super(parentName, parentAge);
this.childName = childName;
this.childAge = childAge;
}

public static Child.ChildBuilder leafChildBuilder() { return new Child.ChildBuilder(); }

public static class ChildBuilder {
private String parentName;
private int parentAge;
private String childName;
private int childAge;

ChildBuilder() {}

public Child.ChildBuilder parentName(String parentName) { this.parentName = parentName; return this; }

public Child.ChildBuilder parentAge(int parentAge) { this.parentAge = parentAge; return this; }

public Child.ChildBuilder childName(String childName) { this.childName = childName; return this; }

public Child.ChildBuilder childAge(int childAge) { this.childAge = childAge; return this; }

public Child build() { return new Child(this.parentName, this.parentAge, this.childName, this.childAge); }

public String toString() {
return "Child.ChildBuilder(parentName=" + this.parentName + ", parentAge=" + this.parentAge + ", childName=" + this.childName + ", childAge=" + this.childAge + ")"; }
}
}

@SuperBuilder class

Parent.class ======>>>

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
public class Parent {
private String parentName;
private int parentAge;

protected Parent(Parent.ParentBuilder<?, ?> b) { this.parentName = b.parentName; this.parentAge = b.parentAge; }

public static Parent.ParentBuilder<?, ?> builder() { return new Parent.ParentBuilderImpl(); }

private static final class ParentBuilderImpl extends Parent.ParentBuilder<Parent, Parent.ParentBuilderImpl> {

private ParentBuilderImpl() {}

protected Parent.ParentBuilderImpl self() { return this; }

public Parent build() { return new Parent(this); }
}

public abstract static class ParentBuilder<C extends Parent, B extends Parent.ParentBuilder<C, B>> {
private String parentName;
private int parentAge;

public ParentBuilder() {}

protected abstract B self();

public abstract C build();

public B parentName(String parentName) { this.parentName = parentName; return this.self(); }

public B parentAge(int parentAge) { this.parentAge = parentAge; return this.self(); }

public String toString() {
return "Parent.ParentBuilder(parentName=" + this.parentName + ", parentAge=" + this.parentAge + ")"; }
}
}

Child.class ======>>>

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
37
38
39
public class Child extends Parent {
private String childName;
private int childAge;

protected Child(Child.ChildBuilder<?, ?> b) {
super(b);
this.childName = b.childName;
this.childAge = b.childAge;
}

public static Child.ChildBuilder<?, ?> builder() { return new Child.ChildBuilderImpl(); }

private static final class ChildBuilderImpl extends Child.ChildBuilder<Child, Child.ChildBuilderImpl> {

private ChildBuilderImpl() {}

protected Child.ChildBuilderImpl self() { return this; }

public Child build() { return new Child(this); }
}

public abstract static class ChildBuilder<C extends Child, B extends Child.ChildBuilder<C, B>> extends ParentBuilder<C, B> {
private String childName;
private int childAge;

public ChildBuilder() {}

protected abstract B self();

public abstract C build();

public B childName(String childName) { this.childName = childName; return this.self(); }

public B childAge(int childAge) { this.childAge = childAge; return this.self(); }

public String toString() {
return "Child.ChildBuilder(super=" + super.toString() + ", childName=" + this.childName + ", childAge=" + this.childAge + ")"; }
}
}

参考资料

Lombok @Builder
Lombok @SuperBuilder
Project Lombok
Lombok @Builder with Inheritance

点击查看

本文标题:关于Lombok使用的一些问题

文章作者:北宸

发布时间:2019年12月28日 - 14:19:25

最后更新:2020年03月17日 - 17:12:31

原始链接:https://www.liaofuzhan.com/posts/4067495121.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------------本文结束 感谢您的阅读-------------------
🌞