冒险契约
53.65M · 2026-02-28
使用 new 关键字(构造函数)和 .builder() 模式(建造者模式)创建对象是两种主要的方式。
简单直接的结论是:对于参数较多(超过3-4个)或包含可选参数的复杂对象,.builder() 更好;对于简单对象,new 更好。
假设有一个 User 类,包含 5 个属性:firstName, lastName, age, phone, address。
new (构造函数)<JAVA>
// 如果有些参数可选,你可能不得不传 null
User user = new User("Zhang", "San", 25, null, "Beijing");
问题:
25 是年龄还是 ID?"Beijing" 是地址还是备注?.builder() (建造者模式)<JAVA>
User user = User.builder()
.firstName("Zhang")
.lastName("San")
.age(25)
.address("Beijing")
.build();
优势:
.age(25) 一目了然。phone)会自动赋默认值或 null,不需要显式传递 null。.address() 再调 .firstName()。new (Constructor)优点:
new 更快)。缺点:
.builder() (Builder Pattern)通常配合 Lombok 的 @Builder 注解使用。
优点:
build() 出来就是最终状态。缺点:
.builder() (推荐)String 类型的字段,用构造函数极其容易传错位置。newnew Point(x, y),比 Point.builder().x(1).y(2).build() 简洁得多。id 和 name,使用构造函数 new Obj(id, name) 可以利用编译器强制约束,而 Builder 默认是允许不调用的(除非在 build() 方法里加校验逻辑)。在现代 Java 开发(尤其是 Spring Boot 项目)中,最流行的做法是 结合 Lombok:
<JAVA>
import lombok.Builder;
import lombok.Data;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private int age;
private String address;
}
用法:
new User()new User(name, age, address)User.builder().name("X").build()总结: 尽量默认使用 @Builder,因为它提供了最好的可读性和维护性,除非你的类非常简单(如 Pair, Point 这种值对象)
build() 出来就是最终状态?Builder 模式本身是为了解决复杂对象的创建问题,而“不可变性”是 Builder 模式最常搭配的“最佳拍档”,但并非“强制绑定”。
Lombok 的 @Builder 只是一个工具,它允许你“离经叛道”地同时使用 @Setter。
想象一下,如果你想设计一个绝对不可变(Immutable)的类 User(即:多线程安全,创建后不可改):
final。这时候问题来了:你怎么给它赋值?
方法 C:用 Builder! 这是唯一的完美解法。Builder 充当了一个缓冲容器,先在 Builder 里面把参数一点点凑齐,最后调用 .build() 的瞬间,一次性把所有参数传给那个私有的全参构造函数。
结论:之所以说 Builder 支持不可变对象,是因为如果没有 Builder,创建复杂的不可变对象极其痛苦。Builder 是实现不可变对象的“最佳助攻”。
@Builder 和 @Data (Setter) 共存?这是 Lombok 为了实用性(Pragmatism) 做出的妥协。
在 Java 的真实开发世界中,有两种截然不同的场景:
@Builder + @Value (或者 @Getter,无 Setter)。@Builder + @Data (包含 Setter)。为什么这么做? 有时候我们仅仅是讨厌写 new User(param1, param2...) 这种长代码,觉得 .builder().name("A").build() 写起来更可读、更优雅。但同时,后续业务逻辑里我又确实需要 user.setStatus(1)。 Lombok 作为一个工具库,它把选择权交给了你:你想用来做防弹衣(不可变),还是做方便面(仅为了好用),都可以。
例:
这是 Builder 被设计出来的初衷。
<JAVA>
@Builder
@Getter // 只有 Get,没有 Set
// 或者直接用 @Value,它会自动把字段设为 private final
public class ImmutableUser {
private final String name;
private final int age;
}
// ImmutableUser user = ImmutableUser.builder().name("A").build();
// user.setName("B"); // 编译报错!根本没有这个方法!
// 安全!
这是为了写代码爽,但牺牲了不可变性。
<JAVA>
@Builder
@Data // 包含了 @Setter
public class MutableUser {
private String name;
private int age;
}
// MutableUser user = MutableUser.builder().name("A").build();
// user.setName("B"); // 可以修改
// 不安全,但在 CRUD 业务中很常见。
@Builder 注解本身在运行时(Runtime)不会创建任何对象,也不会执行 new 操作。
当你编译代码时,Lombok 插件会拦截编译过程,读取你的类定义,然后自动生成大量的 Java 源代码(包括内部静态 Builder 类、构造函数调用等),并将这些生成的代码插入到编译流程中。
你看到的 .class 文件里之所以有 new 操作,是因为 Lombok 在编译阶段已经把包含 new 的代码写好并编译进去了,而不是在运行时动态创建的。
@Builder 是如何工作的?(编译时魔法)Lombok 是一个 注解处理器(Annotation Processor) 。它的工作流程如下:
@Builder。@Builder
public class User {
private String name;
private int age;
}
此时,内存里还没有 Builder 类,也没有 new User(...) 的代码。
编译阶段(关键步骤) :
javac 编译器启动。@Builder。UserBuilder,并生成了 builder() 方法、name() 方法、age() 方法、build() 方法等。new 操作:在生成的 build() 方法中,Lombok 硬编码 了 new User(this.name, this.age) 这样的语句。生成的代码长什么样?
Lombok 实际上把你的类变成了下面这个样子(这是编译器真正处理的内容):
public class User {
private String name;
private int age;
// 1. 私有构造函数(防止外部直接 new User,强制走 Builder)
private User(String name, int age) {
this.name = name;
this.age = age;
}
// 2. 静态方法,返回 Builder 实例
public static UserBuilder builder() {
return new UserBuilder(); // 这里 new 了 Builder 对象
}
// 3. 生成的内部静态 Builder 类
public static class UserBuilder {
private String name;
private int age;
// 链式调用方法
public UserBuilder name(String name) {
this.name = name;
return this;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
// 4. 关键的 build 方法:这里发生了对象的最终创建
public User build() {
// 【重点】:这里的 new User(...) 是 Lombok 在编译时生成的代码!
return new User(this.name, this.age);
}
}
}
new 操作的完整代码编译成 .class 文件。.class 文件。当你调用 User.builder().name("A").build() 时,只是正常执行已经存在的字节码指令(INVOKESPECIAL 即 new 指令)。new ?在 @Builder 的模式下,new 关键字出现在两个地方,但都是编译后存在的:
A. 创建 Builder 对象时
当你调用 User.builder() 时:
return new UserBuilder();B. 创建目标对象时(最关键)
当你调用 .build() 时:
return new User(this.name, this.age);User 对象。private,所以除了 build() 方法内部,其他地方无法直接使用 new User() 。这就是 Builder 模式强制规范对象创建的方式。new ?因为你看到的是原始源码(Source Code) 。
@Builder。new 语句。你在 IntelliJ IDEA 中如果安装了 Lombok 插件,IDE 会“欺骗”你,让你在源码视图也能看到生成的方法(虚线显示),但这只是为了方便你开发(自动补全、跳转)。真正的物理代码是在编译那一刻才“变”出来的。
可以尝试以下操作来验证:
@Builder 的类。Build -> Rebuild Project)。.class 文件(通常在 out/production/... 或 target/classes/... 目录下,或者按 Ctrl+Shift+N 查找类名后选择 .class 后缀的文件)。private User(...) 构造函数和 build() 方法里的 new User(...)。机制:@Builder 是编译时代码生成技术,不是运行时反射或动态代理。new 的位置:new 操作符是由 Lombok 在编译阶段自动写入到生成的 build() 方法和 builder() 方法中的。当你在运行时调用 .build() 时,JVM 执行的是那段早已生成好的 new 指令。