谨慎使用@Builder

大多数同学使用 @Builder 无非就是为了链式编程,然而 @Builder 并不是链式编程的最佳实践,它会额外创建内部类,存在继承关系时还需要使用 @SuperBuilder 注解,设置默认值时也需要额外的 @Builder.Default 去设置默认值,无疑增加了很多不必要的复杂度。

为什么

(1)@Builder 生成的构造器不是完美的,它不能区分哪些参数是必须的,哪些是可选的。如果没有提供必须的参数,构造器可能会创建出不完整或者不合法的对象。

@Builder 注解产生的 Builder 类的构造方法默认并不能限定必传参数。

(2)很多人 喜欢 @Builder@Data 搭配使用,导致生成的构造器是可变的,它允许使用 setter 方法修改构造器的状态。这违反了构造器模式的原则,构造器应该是不可变的,一旦创建就不能被修改。

如果非要使用 @Builder ,那么不要用 @Data ,要用 @Getter。相对来说,反而 @Accessors 的行为更符合这个要求。

(3)@Builder 生成的构造器不适合用于短暂的对象,它会增加代码的复杂度和冗余。构造器模式更适合用于生命周期较长、有多种变体的对象。

实际使用中经常发现 @Builder 滥用的情况,有些仅仅一两个属性的类也都要用 @Builder,真的没必要用,直接用全参的构造方法都比这更简洁。

(4)@Builder 生成的构造器不能处理抽象类型的参数,它只能接受具体类型的对象。这限制了构造器的灵活性和扩展性,不能根据不同的需求创建不同风格的对象。

(5)继承关系时,子类需要使用 @SuperBuilder对象继承后,子类的 Builder 因为构造函数的问题,使用不当大概率会报错,并且无法设置父类的属性,还需要使用 @SuperBuilder 来解决问题。

(6)设置默认值需要使用 @Builder.Default很容易因为对此不了解,导致默认值不符合预期导致出现 BUG。

怎么做

实际运用时,如果属性较多,且分为必传属性和选填属性时,可以将必传参数定义在构造方法中,加上 @Accessors 注解,这样就可以实现必传参数的传入,又可以实现选填参数的链式调用。

假设 Student 类,它的 学生ID和年级和班级是必填的,姓名、性别、住址是选填的,那么示例代码如下

使用 @Accessors 注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import lombok.Data;
import lombok.experimental.Accessors;

@Data
// 使用 @Accessors 注解,设置 chain = true,生成返回 this 的 setter 方法
@Accessors(chain = true)
public class Student {
// 定义必传属性,使用 final 修饰,不提供 setter 方法
private final int studentId; // 学生ID
private final int grade; // 年级
private final int classNum; // 班级

// 定义选填属性,提供 setter 方法
private String name; // 姓名
private String gender; // 性别
private String address; // 住址

// 定义构造方法,接收必传参数
public Student(int studentId, int grade, int classNum) {
this.studentId = studentId;
this.grade = grade;
this.classNum = classNum;
}
}

等价代码

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
44
45
46
47
48
49
50
51
52
53
public class Student {
private final int studentId; // 学生ID
private final int grade; // 年级
private final int classNum; // 班级
private String name; // 姓名
private String gender; // 性别
private String address; // 住址

public Student(int studentId, int grade, int classNum) {
this.studentId = studentId;
this.grade = grade;
this.classNum = classNum;
}

public int getStudentId() {
return this.studentId;
}

public int getGrade() {
return this.grade;
}

public int getClassNum() {
return this.classNum;
}

public String getName() {
return this.name;
}

public Student setName(String name) {
this.name = name;
return this;
}

public String getGender() {
return this.gender;
}

public Student setGender(String gender) {
this.gender = gender;
return this;
}

public String getAddress() {
return this.address;
}

public Student setAddress(String address) {
this.address = address;
return this;
}
}

使用示例

1
2
3
4
Student student = new Student(1001, 3, 8) // 创建一个学生对象,传入必传参数
.setName("张三") // 设置姓名
.setGender("男") // 设置性别
.setAddress("北京市朝阳区"); // 设置住址

参考资料

[^1]: 请谨慎使用 @Builder 注解!