一,产生背景:属性较多的对象,一直分开设置比较麻烦;于是产生了builder方式生成对象。
1.1,分开设置对象的属性
如:
public class Student {
private final int stuId;//必须
private final String name;//必须
private final int age;//可选
private final int gender;//可选
private final String address;//可选
public Student(int stuId,String name){
this(stuId,name,0,1,"");
}
public Student(int stuId,String name,int age){
this(stuId,name,age,1,"");
}
public Student(int stuId,String name,int age,int gender){
this(stuId,name,age,gender,"");
}
public Student(int stuId,String name,int age,int gender,String address){
this.stuId = stuId;
this.name = name;
this.age = age;
this.gender = gender;
this.address = address;
}
}
如此定义对象,要new一个就会是这样:
Student stu = new Student(1,hu,21);
or
Student stu1 = new Student(2,hu,21,1,shenzhen);
or
(如果你定义了set方法)
Student stu2 = new Student();
stu2.setAge(xxx);
stu2.setGender(1);
...
看上去似乎不是很优雅,但是使用builder就可以链式设置对象参数
1.2,builder链式设置对象参数
需要你如下定义对象:
public class Student {
private final int stuId;// 必须
private final String name;// 必须
private final int age;// 可选
private final int gender;// 可选
private final String address;// 可选
private Student(StudentBuilder builder) {
this.stuId = builder.stuId;
this.name = builder.name;
this.age = builder.age;
this.gender = builder.gender;
this.address = builder.address;
}
public int getStuId() {
return stuId;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int getGender() {
return gender;
}
public String getAddress() {
return address;
}
// 自动生成toString方法快捷键,鼠标右键--》source--》generateToString
@Override
public String toString() {
return "Student [stuId=" + stuId + ", name=" + name + ", age=" + age + ", gender=" + gender + ", address="
+ address + "]";
}
public static class StudentBuilder {
// private final int stuId;//将Student的属性用final修饰了,就一定需要先初始化
// 而且,这里我们只提供,get方法获得属性值
// private final String name;//将Student的属性用final修饰了
private int stuId;// 将Student的属性用final修饰了
private String name;// 将Student的属性用final修饰了
private int age;
private int gender;
private String address;
public StudentBuilder() {
}
public StudentBuilder(int stuId, String name) {
this.stuId = stuId;
this.name = name;
}
public StudentBuilder setAge(int age) {
// 将参数赋值给当前对象的属性
this.age = age;
// 返回当前对象
return this;
}
public StudentBuilder setGender(int gender) {
// 将参数赋值给当前对象的属性
this.gender = gender;
// 返回当前对象
return this;
}
public StudentBuilder setAddress(String address) {
this.address = address;
return this;
}
/**
* 最后需要一个build()方法,返回这个设置完参数的对象
*
* @return
*/
public Student build() {
// 返回新建的这个对象
return new Student(this);
}
}
}
使用时,链式设置就行,记得最后来一个build(),构建(返回)这么一个对象:
Student student1 = new Student.StudentBuilder(1, "小明")// 必填属性在构造方法中赋值
.setAge(1)// 设置可选属性 年龄
.setGender(1)// 设置可选属性 性别 默认1为男
.build();// 对象构建完毕的标识,返回Student对象
//没有必设置的值
Student student2 = new Student.StudentBuilder().setAge(21).setGender(2).build();
二,进阶
builder模式另一个重要特性是:它可以对参数进行合法性验证,如果我们传入的参数无效,我们可以抛出一个IllegalStateException异常,但是我们在哪里进行参数合法性验证也是有讲究的:那就是在对象创建之后进行合法性验证。我们修改StudentBuilder的build()方法
public Student build(){
Student student = new Student(this);
if (student.getAge()>120){
throw new IllegalStateException("年龄超出限制");
}
return student;
}
为什么要先创建对象,再进行参数验证?因为我们的StudentBuilder是线程不安全的,如果我们先进行参数验证后创建对象,那么创建对象的时候对象的属性可能已经被其他线程改变了,例如下面的代码就是错误的
/**
* 错误的
*/
public Student build(){
if (age>120){
throw new IllegalStateException("年龄超出限制");
}
return new Student(this);
}