外观
优雅构建 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();感谢团队的所有小伙伴,集思广益。