
本文详解如何借助 Lombok 的 @With 注解,为含集合字段(如 Set skills)的不可变 @Value 类生成语义清晰、线程安全的“with-方法”,实现单字段(尤其是集合)的原子级替换,避免 toBuilder() 在集合重置场景下的失效问题。
本文详解如何借助 lombok 的 `@with` 注解,为含集合字段(如 `set
在使用 Lombok 构建不可变值对象(@Value)时,开发者常依赖 @Builder(toBuilder = true) 实现“基于原实例的修改”。但当目标字段是集合类型(如 @Singular Set
根本原因在于:@Singular 生成的 builder 方法(如 .skill("Java"))专为增量添加设计;而 .skills(...) 是普通 setter 风格方法,在 toBuilder() 流程中可能被跳过、覆盖或因不可变性约束而失效。
此时,@With 注解提供了更直接、更可靠的替代方案。它为指定字段自动生成 withXxx(...) 方法——该方法创建并返回一个全新实例,其中仅目标字段被赋予新值,其余字段保持原样,天然契合不可变对象的设计哲学。
✅ 正确用法:@With 替代 toBuilder() 处理集合替换
import lombok.Value;
import lombok.Builder;
import lombok.With;
import java.util.Set;
@Value
@Builder
public class Person {
String name;
// 关键:显式添加 @With,使 skills 字段支持 withSkills()
@With
@Singular
Set<string> skills;
}</string>
使用示例:
Person john = Person.builder()
.name("John")
.build();
System.out.println(john);
// 输出:Person(name=John, skills=[])
// ✅ 使用 withSkills() 直接替换整个集合(非追加!)
Person johnUpgraded = john.withSkills(Set.of("Java", "Spring"));
System.out.println(johnUpgraded);
// 输出:Person(name=John, skills=[Java, Spring])
// ✅ 轻松重置为空集合
Person johnDowngraded = johnUpgraded.withSkills(Set.of());
System.out.println(johnDowngraded);
// 输出:Person(name=John, skills=[])
? 注意:@With 默认仅对非 final 字段生效。由于 @Value 自动生成 final 字段,需确保 Lombok 版本 ≥ 1.18.20,并在项目根目录 lombok.config 中启用全局支持:
lombok.with.flag = true否则编译时会报错 “Cannot generate @With method for final field”。
⚠️ 重要补充:@With 是浅拷贝,嵌套集合需额外处理
需明确:@With 本身不递归克隆集合内部元素,它只保证外层对象不可变。若 skills 是自定义对象集合(如 Set
- 若 Skill 也是 @Value/不可变类 → 安全;
- 若 Skill 可变 → 在 withSkills() 前手动深拷贝每个元素,例如:
Set<skill> newSkills = originalSkills.stream() .map(skill -> new Skill(skill.getName(), skill.getLevel())) // 手动构造新实例 .collect(Collectors.toSet()); Person updated = person.withSkills(newSkills);</skill>
✅ 总结:何时选择 @With?
| 场景 | 推荐方式 | 理由 |
|---|---|---|
| ✅ 单字段(尤其集合)整体替换 | @With + withXxx(...) | 语义明确、链式简洁、无 builder 嵌套、天然不可变 |
| ⚠️ 多字段批量更新 + 需复用 builder 逻辑 | toBuilder() + @Singular 追加方法 | 适合增量构建,但不适用于集合清空/全量替换 |
| ? 深层嵌套对象字段更新(如 person.address.city) | 组合 @With(各层均标注) | 比多层 toBuilder().address().toBuilder().city(...).build().build() 更扁平易读 |
通过 @With,你获得的不仅是一个便捷方法,更是一种声明式、高内聚的不可变更新范式——让代码意图一目了然,让集合操作真正可控。











