《Effective Java》第二条:多构造器参数时考虑使用构建器
·
一、前置总结
在创建类时,若需要初始化多个参数,重叠构造器模式可行,但仍不利于客户端编写,且难以阅读;JavaBeans模式可行,但是会使得类可能处于不一致状态。而构建器Builder能够较好的处理多参数情况。
二、细节
如果用盖房子比作创建类,属性相当于房子中家具的品牌。
1. 重叠构造器模式(一味堆叠家具的房子,很容易乱。捏码,谁买的苹果牌抽油烟机?)
多参数情况,在业务中用于封装业务数据的类中很常见。如果利用构造器创建类,如下:
public class Person {
private final String name; // 必填字段
private final int age; // 必填字段
private final String phone; // 可选字段
private final String address; // 可选字段
// 基础构造器(仅必填字段)
public Person(String name, int age) {
this(name, age, null);
}
// 扩展构造器(增加phone字段)
public Person(String name, int age, String phone) {
this(name, age, phone, null);
}
// 全参构造器(所有字段)
public Person(String name, int age, String phone, String address) {
this.name = name;
this.age = age;
this.phone = phone;
this.address = address;
}
// Getter方法
public String getName() { return name; }
public int getAge() { return age; }
public String getPhone() { return phone; }
public String getAddress() { return address; }
}
// 仅初始化必填字段
Person p1 = new Person("Alice", 25);
// 初始化必填字段+phone
Person p2 = new Person("Bob", 30, "13800138000");
// 初始化全部字段
Person p3 = new Person("Charlie", 35, "13900139000", "Beijing");
这要求程序员在编写代码时知道参数顺序,这使得参数量越多,编码成本越大。加入上述Person类在创建时,“phone”和“address”两个参数的输入颠倒,编译器也不会出错,但是闹笑话了。
2. JavaBeans模式(先规划房子的空间,然后放家具,容易出现家具还未买好,人已入住的情况。虚假广告,退钱。)
public class User implements Serializable {
// 必需:无参构造器
public User() {}
// 私有字段(属性)
private String name;
private int age;
private boolean active;
// Getter 和 Setter 方法(标准命名规范)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// Boolean 类型属性的特殊命名
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
}
JavaBeans模式最大缺点在于使得类可能处于不一致状态。
User user = new User();
user.setName("xxx");
user.getAge();
在还未初始化属性age的情况下,直接调用,然后报错。这是由于JavaBeans:
-
无参构造函数无法校验必要属性无参构造函数不接收任何参数,因此无法在对象创建时强制要求初始化必要属性。它只能将属性设置为默认值,而无法保证 “对象创建即有效”。
-
setter 方法是 “可选操作”
setter方法的调用完全依赖开发者的自觉性:- 开发者可能忘记调用某个必要属性的
setter(如漏设password); - 可能在设置完部分属性后,就将对象传递给其他方法使用(如提前存入数据库、传入校验逻辑)。
- 开发者可能忘记调用某个必要属性的
-
对象创建与初始化分离JavaBeans 的设计将 “对象创建” 和 “属性初始化” 拆分为两个独立步骤,而没有机制保证 “初始化完成前对象不可用”。这与 “构造函数一次性初始化所有必要属性” 的模式(如带参构造)形成鲜明对比。
3. Builder(建造者)模式(委托一个装潢师傅买家具,师傅会提醒你哪些家具必须你定好牌子,哪些你不说,师傅就买默认的牌子,一手打造可以拎包入住的房子。五星好评!!!)
public class User {
// 必需属性
private final String username;
// 可选属性
private final String email;
private final int age;
private final String phone;
// 私有构造器(仅Builder可访问)
private User(Builder builder) {
this.username = builder.username;
this.email = builder.email;
this.age = builder.age;
this.phone = builder.phone;
}
// Builder静态内部类
public static class Builder {
private final String username; // 必需属性
private String email = "";
private int age = 0;
private String phone = "";
// 必需参数构造器
public Builder(String username) {
this.username = username;
}
// 可选参数设置方法(返回Builder实现链式调用)
public Builder email(String email) {
this.email = email;
return this;
}
public Builder age(int age) {
this.age = age;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
// 构建最终对象
public User build() {
return new User(this);
}
}
// 示例用法
public static void main(String[] args) {
User user = new User.Builder("john_doe")
.email("john@example.com")
.age(30)
.phone("123-456-7890")
.build();
}
}
等到后续相关条目看完补充。
三、总结
Builder模式在创建类时,需要创建构建器,有性能开销;且代码也比较冗长。《EJ》推荐一般在有4个以上时用它,毕竟谁会为了几个家具请师傅呢?
如果将来有添加参数的可能,一开始就使用构建器更好。
更多推荐



所有评论(0)