参数较多时存在的问题
当一个类需要多个参数(尤其是大部分为可选参数)时,传统方案存在明显缺陷:
传统模式的缺点
- 重叠构造器(Telescoping Constructor) :为不同参数组合创建多个构造器,参数越多,构造器数量呈指数级增长,可读性极差,调用时容易混淆参数顺序(如
new User(1, "张三", 25, null, "male", " Beijing"),难以快速对应参数含义)。
实例:
java
public class User {
// 必要参数
private final long id;
private final String name;
// 可选参数
private final int age;
private final String email;
private final String gender;
private final String address;
// 仅包含必要参数的构造器(基础版)
public User(long id, String name) {
this(id, name, 0); // 调用下一个构造器,默认age为0
}
// 增加1个可选参数(age)
public User(long id, String name, int age) {
this(id, name, age, null); // 默认email为null
}
// 再增加1个可选参数(email)
public User(long id, String name, int age, String email) {
this(id, name, age, email, "unknown"); // 默认gender为unknown
}
// 再增加1个可选参数(gender)
public User(long id, String name, int age, String email, String gender) {
this(id, name, age, email, gender, "unknown"); // 默认address为unknown
}
// 包含所有参数的构造器(完整版)
public User(long id, String name, int age, String email, String gender, String address) {
this.id = id;
this.name = name;
this.age = age;
this.email = email;
this.gender = gender;
this.address = address;
}
}
java
// 只想设置id、name、address,但必须传递age、email、gender的默认值
User user = new User(1001, "张三", 0, null, "unknown", "北京");
- 参数越多,构造器数量爆炸(
n个参数需要n个构造器); - 调用时难以区分参数含义(如
0、null到底对应哪个属性),容易因顺序错误导致 bug; - 可读性极差,维护成本高。
-
JavaBean 模式 :通过无参构造器创建对象,再用
setter方法设置属性。缺点是:- 对象创建过程中可能处于 "不一致状态"(部分属性未设置完成时被使用);
- 无法保证对象不可变(
setter破坏封装性,多线程环境下需额外处理同步)。
核心思路 :通过无参构造器创建对象,再用setter方法逐一设置属性。
java
public class User {
// 必要参数(非final,允许修改)
private long id;
private String name;
// 可选参数
private int age;
private String email;
private String gender;
private String address;
// 无参构造器(必须)
public User() {}
// 所有参数的setter方法
public void setId(long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setEmail(String email) {
this.email = email;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setAddress(String address) {
this.address = address;
}
}
//调用方式:
User user = new User();
user.setId(1001);
user.setName("张三");
user.setAddress("北京"); // 按需设置属性
问题:
- 对象创建过程中处于 "不一致状态"(例如仅调用
setId后就被其他线程使用,导致属性不完整); - 无法保证对象不可变(
setter方法允许随时修改属性),多线程环境下需额外处理同步; - 缺少参数校验的 "时机"(若在
setter中校验,仍可能出现部分属性未校验的中间状态)
解决方案:Builder模式(生成器)
核心思想 :不直接通过构造器或setter创建对象,而是先通过一个 "生成器(Builder)" 对象设置所有参数,最后调用build()方法生成不可变的目标对象。
java
public class User {
// 必要参数
private final long id;
private final String name;
// 可选参数
private final int age;
private final String email;
private final String gender;
private final String address;
// 私有构造器,仅允许Builder调用
private User(Builder builder) {
this.id = builder.id;
this.name = builder.name;
this.age = builder.age;
this.email = builder.email;
this.gender = builder.gender;
this.address = builder.address;
// 参数验证(如id不可为负,name不可为空)
if (id <= 0) throw new IllegalArgumentException("id must be positive");
if (name == null || name.isEmpty()) throw new IllegalArgumentException("name is required");
}
// 静态内部类:Builder
public static class Builder {
// 必要参数(无默认值,必须设置)
private final long id;
private final String name;
// 可选参数(有默认值)
private int age = 0;
private String email = null;
private String gender = "unknown";
private String address = "unknown";
// Builder的构造器:仅初始化必要参数
public Builder(long id, String name) {
this.id = id;
this.name = name;
}
// 链式设置方法(返回Builder自身)
public Builder age(int age) {
this.age = age;
return this;
}
public Builder email(String email) {
this.email = email;
return this;
}
// 其他setter...
// 构建目标对象
public User build() {
return new User(this);
}
}
}
调用方式:
java
User user = new User.Builder(1001, "张三")
.age(25)
.email("zhangsan@example.com")
.address("北京")
.build();
Builder 模式的优势
- 可读性强 :参数名与值一一对应(如
.age(25)),无需记忆参数顺序; - 保证对象不可变 :目标类所有属性为
final,且无setter,创建后状态固定,线程安全; - 参数验证集中 :在
build()方法中统一验证,避免对象处于不一致状态; - 支持可变参数集合 :可在
Builder中灵活处理多个同类型参数(如addHobby(String hobby))。
适用场景与注意事项
-
适用场景:类有多个参数(尤其是可选参数多),或未来可能扩展更多参数的场景(如 POJO、配置类)。
-
注意事项:
- 增加了代码量(需定义
Builder类),但对于参数多的类,可读性提升远大于代码量增加的成本; Builder不适合参数极少的类(如仅 2-3 个必要参数,直接用构造器更简洁);- 若目标类需继承,
Builder的实现会更复杂(需考虑子类Builder与父类的兼容性)。
- 增加了代码量(需定义
生成器模式非常适合类层次结构
与此同时,在《Effective Java》条目 2 中,提到 "生成器模式非常适合类层次结构",核心是指:当存在父类与子类构成的继承关系(类层次结构)时,使用生成器(Builder)可以优雅地解决多参数构造场景下的继承兼容性问题,避免传统构造器或 JavaBean 模式在继承中出现的混乱。
具体场景与问题
假设存在一个类层次结构:比如父类Shape(图形),子类Circle(圆形)、Rectangle(矩形)。每个类都有自己的必要参数和可选参数(如Shape有color,Circle额外有radius,Rectangle额外有width和height)。
若用传统构造器实现继承,会出现两个问题:
- 子类构造器必须传递父类的所有参数 ,导致参数列表冗长且混乱(如
Circle的构造器可能需要(color, radius, ...),Rectangle则需要(color, width, height, ...)); - 无法灵活扩展:若父类新增参数,所有子类的构造器都需修改,维护成本极高。
生成器模式如何适配类层次结构?
核心方案是:让父类和子类各自定义对应的 Builder,并通过 "泛型 + 递归类型参数" 让子类 Builder 能返回自身类型,实现链式调用的连贯性。具体步骤如下:
1. 父类定义抽象 Builder(泛型约束)
父类的 Builder 为抽象类,通过泛型指定 "子类 Builder 的类型",并定义父类属性的设置方法(返回泛型类型,确保子类调用时仍能链式返回子类 Builder)。
示例(父类Shape):
java
public abstract class Shape {
private final String color; // 父类的必要参数
// 父类的构造器,由子类Builder调用
protected Shape(Builder<?> builder) {
this.color = builder.color;
}
// 父类的抽象Builder,泛型T约束为子类Builder
public abstract static class Builder<T extends Builder<T>> {
protected String color; // 父类的参数
// 父类参数的设置方法,返回T(子类Builder类型)
public T color(String color) {
this.color = color;
return self(); // 关键:返回子类Builder实例
}
// 抽象方法:由子类实现,返回自身(确保链式调用类型正确)
protected abstract T self();
// 抽象build方法:由子类实现,返回具体子类实例
public abstract Shape build();
}
}
2. 子类定义具体 Builder(继承父类 Builder 并指定泛型)
子类的 Builder 继承父类 Builder,并将泛型参数指定为自身类型(如Circle.Builder extends Shape.Builder<Circle.Builder>),同时添加子类特有的参数设置方法,最终通过build()返回子类实例。
示例(子类Circle):
java
public class Circle extends Shape {
private final double radius; // 子类的必要参数
// 子类的构造器,接收子类Builder
private Circle(Builder builder) {
super(builder); // 调用父类构造器,传递父类参数
this.radius = builder.radius;
}
// 子类的Builder,泛型指定为自身(Circle.Builder)
public static class Builder extends Shape.Builder<Builder> {
private final double radius; // 子类的必要参数(必须在Builder构造器中初始化)
// 子类Builder的构造器:初始化子类的必要参数
public Builder(double radius) {
this.radius = radius;
}
// 实现父类的self(),返回当前Builder实例(确保链式调用类型正确)
@Override
protected Builder self() {
return this;
}
// 实现build(),返回Circle实例
@Override
public Circle build() {
return new Circle(this);
}
}
}
3.调用方式
java
// 创建Circle:先设置父类参数(color),再通过build()返回Circle
Circle circle = new Circle.Builder(5.0) // 子类必要参数radius
.color("red") // 父类参数,返回Circle.Builder,支持链式调用
.build();
核心优势
- 链式调用连贯性 :通过泛型
T extends Builder<T>和self()方法,子类 Builder 调用父类的设置方法后,仍能返回子类 Builder 类型 ,确保链式调用不中断(如circleBuilder.color().build()仍能正确返回Circle)。 - 参数隔离与扩展:父类参数由父类 Builder 管理,子类参数由子类 Builder 管理,新增父类参数时,仅需修改父类 Builder,子类无需变动;新增子类参数时,仅需扩展子类 Builder,不影响父类和其他子类。
- 类型安全 :编译期即可保证
build()返回的是具体子类类型(如Circle.Builder的build()返回Circle),无需强制类型转换。