跳到内容

← 返回知识库WIKI/代码示例/ARTICLE

优雅构建 Bean 对象的终极方案探索——从 Set 地狱到泛型 + Lambda 链式构造 ​

一、背景 ​

公司最近在做一个老项目改造,其中在做 java 对象赋值的时候,set 方法写的看着都头蒙,简直是一种"看似无害但累积成灾"的代码模式。

可以看下下面的这种写法,如果属性多的话,不仅代码冗长,而且可读性和维护性差。这种"先 new 再 set"的方式被称为 Set Hell(设值地狱)。

java
// 创建对象1
User user1 = new User();
user1.setName("张三");
user1.setAge(18);
UserService.listByParam(user1);
// 创建对象2
User user2 = new User();
user2.setName("李四");
user2.setAge(19);
UserService.listByParam(user2);

二、方案思考 ​

为了解决这个问题,我们考虑了几种方案进行实现。

1. Builder 模式(链式构造) ​

使用 Lombok 或手写 Builder 类,实现流畅 API:

java
UserService.listByParam(
    new User.Builder().name("张三").age(18).build()
);

缺点:

  • 因为这是一个老项目,我们没办法引入新的 jar 包
  • 必须修改原 Bean 类结构,对已有类侵入性强
  • 每个类都要生成 Builder,工作量大

2. 构造函数解决 ​

缺点:

  • 构造函数参数过多时难以维护(如超过 5 个字段)
  • 不支持可选字段,灵活性差
  • 同样需修改源码,不适用于外部类

3. 反射动态赋值(BeanBuilder + 字符串属性名) ​

尝试通过反射绕过编译限制:

java
BeanBuilder.create(new User())
  .with("name", "张三")
  .with("age", 18)
  .build();

这个方法基本上满足了我们的诉求点,完全动态,无需修改原类。

缺点:

  • 属性名为字符串,编译期无法校验,易出错
  • 反射性能较差,且需处理访问权限(private 字段)
  • IDE 无法自动补全,调试困难,维护成本高

三、原始诉求重新梳理 ​

其实我们只是想找到一个方案来替换掉 Bean 构建时多 set 的问题,新的方案应该满足以下特性:

  • 类型安全:编译期能检查错误
  • 零侵入:不修改原有 Bean 类
  • 性能良好:避免反射开销
  • 语法优雅:支持链式调用,代码紧凑
  • 兼容性好:可复用已有实例

经过不断的思考,终于找到了一个方案:利用 Java 8 的 Method Reference + 泛型回调机制。

四、最终代码实现 ​

1. 设计思路 ​

将 setter 方法作为函数式接口传入,由 Builder 触发执行,底层仍是直接调用 set 方法,零反射、高性能、类型安全。

2. 完整源码实现 ​

java
import java.util.Objects;

/**
 * 通用链式Bean构建器
 * 支持无侵入式动态赋值,基于Method Reference实现类型安全 & 高性能
 *
 * @param <T> 目标Bean类型
 */
public class BeanBuilder<T> {
    private final T instance;

    private BeanBuilder(T instance) {
        this.instance = instance;
    }

    /**
     * 设置字段值(通过Setter Method Reference)
     */
    public <K> BeanBuilder<T> with(HandlerPushData<T, K> setter, K value) {
        if (instance != null && value != null) {
            setter.handler(instance, value);
        }
        return this;
    }

    /**
     * 获取构建后的对象
     */
    public T build() {
        return instance;
    }

    // --- Static Factory Methods ---

    /**
     * 基于已有对象进行修改
     */
    public static <T> BeanBuilder<T> of(T instance) {
        return new BeanBuilder<>(instance);
    }

    /**
     * 基于新创建的对象(推荐配合 ::new 使用)
     */
    public static <T> BeanBuilder<T> create(T instance) {
        return new BeanBuilder<>(instance);
    }

    /**
     * 使用构造器Supplier创建新实例
     */
    public static <T> BeanBuilder<T> create(HandlerCreate<T> creator) {
        return new BeanBuilder<>(creator.handler());
    }

    // --- Functional Interfaces ---

    @FunctionalInterface
    public interface HandlerPushData<T, K> {
        void handler(T bean, K value);
    }

    @FunctionalInterface
    public interface HandlerCreate<T> {
        T handler();
    }
}

3. 最终调用形式 ​

这里举例的字段比较少,如果字段比较多的情况下,可以明显地看出来链式调用的优缺点及代码的优雅性。

java
// 新建对象并设置
BeanBuilder.create(User::new)
    .with(User::setName, "张三")
    .with(User::setAge, 18)
    .build();

// 或基于已有对象修改(非破坏式)
User existingUser = getUserFromCache();
User newUser = BeanBuilder.of(existingUser)
    .with(User::setName, "李四")
    .build();

感谢团队的所有小伙伴,集思广益。