一,產生背景:屬性較多的對象,一直分開設置比較麻煩;于是產生了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);
}