在Java项目里,当一个类拥有大量成员变量,尤其是其中部分字段可选、部分字段存在默认值或相互约束时,使用多参数构造器或者一堆setter方法都会让调用端代码变得笨重且容易出错。Lombok的@Builder注解通过自动生成建造者模式相关代码,让开发者以链式、可读性强的方式完成复杂对象的构建。它并不是运行时通过反射去创建对象,而是在编译阶段修改抽象语法树,直接写出对应的Builder内部类与构建方法。

@Builder注解的基本用法与编译原理
在一个普通POJO类上添加@Builder之后,Lombok会在编译期为该类生成一个名为ClassNameBuilder的静态内部类。这个内部类为每个实例字段都提供了一个同名且返回Builder自身的方法,最后通过build()方法调用全参构造器生成目标对象。由于这些代码是编译期生成的,运行时没有任何额外依赖和性能损耗,反编译后的class文件和手写建造者几乎没有区别。
从原理上看,Lombok使用的是Java注解处理器结合私有AST修改机制。它在javac编译流程的解析阶段之后介入,将带有@Builder的类节点进行改写,插入Builder类定义以及对应的字段赋值逻辑。正因为如此,IDE里虽然源码看不到Builder类,但在开启注解处理的情况下,编写User.builder().name("tom").age(20).build()这样的代码时不会报找不到符号的错误。
下面给出一个最基础的实战示例,展示实体类与构建调用的写法:
import lombok.Builder;
import lombok.ToString;
@Builder
@ToString
public class User {
private String name;
private int age;
private String email;
}
// 调用端代码
public class Demo {
public static void main(String[] args) {
User user = User.builder()
.name("张三")
.age(28)
.email("test@ipipp.com")
.build();
System.out.println(user);
}
}
上述代码中,email字段如果不设置,构建出来的对象该字段就是null。这种写法比使用包含三个参数的构造器User(String, int, String)要清晰很多,调用者一眼就能明白每个值对应哪个业务含义,不会因为参数位置写错而导致隐蔽的Bug。
结合@Builder.Default处理字段默认值
在使用@Builder时有一个常见的坑:如果直接在字段上赋予初始值,例如private String status = "ACTIVE";,通过Builder构建对象时若没有显式设置该字段,最终对象里的status会是null而不是"ACTIVE"。原因是Lombok生成的Builder在build()时通过全参构造器传入了Builder内部持有的默认值,而Builder内部该字段的默认值是对应类型的零值,并没有读取原字段的初始赋值。
为了解决这个问题,Lombok提供了@Builder.Default注解。只要在字段的初始值定义上加上它,Lombok就会把该默认值正确地同步到Builder类中,保证未显式传参时使用预设值。这个细节在配置类、枚举状态类里非常关键,否则会让系统出现意料之外的空状态。
示例代码如下,展示默认值注解的正确使用方式:
import lombok.Builder;
import lombok.Builder.Default;
@Builder
public class Order {
private String orderId;
@Default
private String status = "CREATED";
@Default
private boolean paid = false;
private String remark;
}
// 构建时不设置status和paid
Order order = Order.builder()
.orderId("OD001")
.build();
// order.getStatus() 返回 "CREATED"
// order.isPaid() 返回 false
从可维护性角度看,把默认值声明和字段定义放在一起,比在构造器或Builder类里散落地写默认值要直观得多。当业务变更默认状态时,只需要改动一处注解字段,不需要去翻找Builder生成逻辑或者手写构造器。
在继承场景与必填项校验中的实战技巧
默认情况下,@Builder不能直接用于有父类的子类并继承父类的字段构建能力。如果类A继承类B,只在子类加@Builder,父类字段不会出现在Builder方法里。Lombok给出了@SuperBuilder注解来解决继承链上的建造者生成,它要求父类和子类都使用@SuperBuilder,然后生成的Builder具备跨层级的链式设置能力。
另一个实战中很有用的点是必填项校验。虽然@Builder本身不提供约束,但可以在类中写一个私有的全参构造器,并在其中对关键字段做校验,或者使用@Builder的builderMethodName自定义Builder方法,在build()前通过@Builder.ObtainVia等方式做检查。更简单的做法是在build()之后由业务层校验,或者在Builder类生成后配合Jakarta Bean Validation的@NotNull注解,在构建完成后统一validate。
下面演示@SuperBuilder的基础用法,以及如何在子类构建时同时设置父类字段:
import lombok.experimental.SuperBuilder;
import lombok.Getter;
@Getter
@SuperBuilder
class BaseConfig {
private String env;
private int timeout;
}
@Getter
@SuperBuilder
class DbConfig extends BaseConfig {
private String url;
private String username;
}
// 使用方式
DbConfig db = DbConfig.builder()
.env("prod")
.timeout(3000)
.url("jdbc:mysql://127.0.0.1:3306/test")
.username("root")
.build();
在复杂领域模型里,这种跨层构建能力极大减少了为了传参而写的冗余子类构造器。同时,由于Builder方法都是返回自身类型,IDE的自动补全可以引导开发者把所有必填项补齐,相比直接new DbConfig("prod", 3000, null, null, "root")这类长构造器,出错概率明显下降。对于团队代码规范来说,统一采用@Builder或@SuperBuilder也能让复杂对象的初始化风格保持一致。