谨慎使用@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) public class Student { private final int studentId; 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; } }
|
等价代码
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; 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 注解!