builder 类不必是静态内部类,但强烈建议;build() 应在最后统一校验;lombok @builder 多数场景可用,但有三限制;链式调用返回 this 为提效与保序,但不线程安全。

Builder 类必须是目标类的静态内部类吗?
不是必须,但强烈建议。非静态内部类会隐式持有外部类实例引用,可能引发内存泄漏或意外状态耦合;而 static 内部类更轻量、语义更清晰,也方便单独测试。
- 如果 Builder 需要访问目标类的私有字段(比如做校验),用
static+ 友元式构造(通过 public 构造函数传参)比非静态更可控 - 若 Builder 被提取为顶级类(如
PersonBuilder),务必确保它和目标类(如Person)版本同步,否则容易出现字段不一致导致的NullPointerException - IDE 自动生成 Builder 时(如 Lombok 的
@Builder),默认生成static,别手动改成非静态除非真有跨生命周期共享状态的需求
build() 方法里该不该做参数校验?
应该做,而且得在 build() 调用时一次性校验,不是在每个 withXxx() 方法里分散校验。
- 提前校验(比如在
withName(String)里 throw NullPointerException)会导致链式调用中断,用户无法“先设完再统一检查” - 延迟到
build()才校验,能收集所有必填字段缺失情况,报错信息可合并(例如:“name 和 age 都不能为空”),体验更好 - 注意校验逻辑不要依赖外部状态(如数据库、系统时间),否则
build()就不再是纯函数,难以单元测试
Lombok 的 @Builder 能直接替代手写 Builder 吗?
大多数场景可以,但要注意三个硬限制:
- 不支持条件性必填字段(比如“如果设置了
type = "VIP",则vipLevel必须非空”),Lombok 生成的build()没法插入手动校验逻辑 - 生成的 Builder 类名默认是
TargetClass.Builder,但如果目标类已存在静态内部类Builder,Lombok 会静默失败,编译报错提示 “duplicate class”,得加@Builder(builderClassName = "CustomBuilder") - 泛型类型推导有时不准,比如构建
List<string></string>字段时,builder.items(Arrays.asList("a"))可能触发类型擦除问题,需显式写成builder.<string>items(...)</string>
为什么链式调用返回 this 而不是新 Builder 实例?
为了节省对象分配开销,并保证调用顺序与字段赋值顺序严格一致。每次 new 一个 Builder 实例看似“不可变”,实则破坏了构建上下文的连贯性。
立即学习“Java免费学习笔记(深入)”;
- 返回新实例会让用户误以为调用
withName("A").withName("B")最终 name 是 "B" —— 确实如此,但中间那个带 "A" 的 Builder 白建了,GC 压力增大 - 返回
this是主流做法,但要注意线程不安全:Builder 实例不能被多个线程共用,否则会出现字段覆盖(比如 A 线程 set age=20,B 线程 set age=30,最终 build 出来可能是 30,也可能因指令重排出错) - 如果真需要线程安全构建,别用链式,改用传统 setter + 显式
build(),或者用不可变 Builder(每次 set 都 copy 新实例),但代价是堆内存翻倍









