
本文介绍如何借助 lens 函数式编程模式,在不修改原始不可变 pojo 的前提下,安全、简洁、可组合地更新多层嵌套字段(如 8 层深),避免冗长易错的 tobuilder 链式调用与空指针检查。
本文介绍如何借助 lens 函数式编程模式,在不修改原始不可变 pojo 的前提下,安全、简洁、可组合地更新多层嵌套字段(如 8 层深),避免冗长易错的 tobuilder 链式调用与空指针检查。
在基于 Lombok @Value + @Builder(toBuilder = true) 构建的不可变对象体系中,更新深层字段(例如 ClassA → ClassB → ClassD → fieldD)需手动逐层重建对象,代码极易膨胀且脆弱:
classA = classA.toBuilder()
.classB(classA.getClassB().toBuilder()
.classD(classA.getClassB().getClassD().toBuilder()
.fieldD("abc")
.build())
.build())
.build();当嵌套达 5–8 层,且各层级对象可能为 null 时,还需插入大量判空逻辑,可维护性急剧下降。
Lens 提供了一种声明式、可组合的替代方案。其核心思想是:每个字段封装为一个 Lens<root field></root>,它同时支持「获取」(get)和「更新」(modify/set)能力;多个 Lens 可通过 then() 方法链式组合,最终形成一条从根对象直达目标字段的“聚焦路径”。
以下是轻量级 Lens 接口实现(无外部依赖):
interface Lens<Root, T> {
T get(Root r);
Root modify(Root root, UnaryOperator<T> f);
default Root set(Root root, T newValue) {
return modify(root, x -> newValue);
}
default <U> Lens<Root, U> then(Lens<T, U> next) {
return new Lens<>() {
@Override
public U get(Root r) {
T inner = Lens.this.get(r);
return inner == null ? null : next.get(inner);
}
@Override
public Root modify(Root r, UnaryOperator<U> f) {
if (r == null) return null;
T inner = Lens.this.get(r);
if (inner == null) return r; // 或抛异常,按需定制
T updatedInner = next.modify(inner, f);
return Lens.this.modify(r, x -> updatedInner);
}
};
}
}关键在于 @With 注解(Lombok 1.18.20+ 支持)——它为每个字段自动生成 withXxx() 方法(返回新实例),完美替代 toBuilder().xxx(...).build()。配合工厂方法,可统一处理 null 安全:
static <Root, T> Lens<Root, T> make(
Function<Root, T> getter,
BiFunction<Root, T, Root> wither) {
return new Lens<>() {
@Override
public T get(Root r) {
return r == null ? null : getter.apply(r);
}
@Override
public Root modify(Root r, UnaryOperator<T> f) {
if (r == null) return null;
T current = getter.apply(r);
T updated = (current == null) ? null : f.apply(current);
return wither.apply(r, updated);
}
};
}在类中声明静态 Lens 字段(推荐用 IDE 生成或注解处理器自动化):
@Value
@With
public class ClassA {
private String fieldA;
private ClassB classB;
public static final Lens<ClassA, String> FIELD_A_LENS
= make(ClassA::getFieldA, ClassA::withFieldA);
public static final Lens<ClassA, ClassB> CLASS_B_LENS
= make(ClassA::getClassB, ClassA::withClassB);
}
@Value
@With
public class ClassB {
private ClassC classC;
private ClassD classD;
public static final Lens<ClassB, ClassC> CLASS_C_LENS
= make(ClassB::getClassC, ClassB::withClassC);
public static final Lens<ClassB, ClassD> CLASS_D_LENS
= make(ClassB::getClassD, ClassB::withClassD);
}
@Value
@With
public class ClassD {
private String fieldD;
public static final Lens<ClassD, String> FIELD_D_LENS
= make(ClassD::getFieldD, ClassD::withFieldD);
}现在,更新 classA.classB.classD.fieldD 只需一行清晰、可读、可复用的代码:
ClassA updated = ClassA.CLASS_B_LENS
.then(ClassB.CLASS_D_LENS)
.then(ClassD.FIELD_D_LENS)
.set(classA, "New Value!");✅ 优势总结:
-
零侵入:无需修改现有
@Value类结构,仅新增静态 Lens 字段; -
空安全:
make()工厂内置null处理策略(可按业务定制为跳过、抛异常或默认值); -
高度可组合:任意层级字段均可拼接,支持复用(如
CLASS_B_LENS.then(ClassB.CLASS_C_LENS)); - 类型安全 & IDE 友好:全程泛型推导,编译期检查,支持自动补全;
- 易于测试与扩展:Lens 本身是纯函数接口,可轻松 mock 或增强(如添加日志、校验)。
⚠️ 注意事项:
- 确保所有中间类均启用
@With(非@Builder),否则缺少withXxx()方法; - 若某层字段允许为
null,then()中的get()返回null时,后续modify()行为需明确定义(当前示例选择透传原对象,你也可改为抛NullPointerException或返回Optional); - 对超大型项目,建议结合 Annotation Processor 自动生成 Lens 字段,避免手写错误与维护成本。
Lens 并非银弹,但它将“对象更新”这一命令式操作,升华为一种可组合、可推理、可重用的函数式抽象——在不可变数据盛行的现代 Java 架构(如响应式流、领域驱动设计 DDD 值对象)中,是值得纳入工具箱的关键范式。

















