本文是 《Effective Java》的讀書筆記,由於是Java進階書,難免會有理解的偏差,如有錯誤,非常歡迎能批評指正,本人不勝感激。
什麼是構建器(Builder)呢,參加過面試的同學可能會有被問到String和StringBuffer和StringBuilder的區別,這裡就有StringBuilder這個字串構建器,我們來簡單看一下這個構建器的用法吧。
String sb = new StringBuilder().append("I ") .append("am ") .append("student.").toString();
我們看一下這裡用的append()函數的源碼
@Override public StringBuilder append(String str) { super.append(str); return this; }
可知這個append()將傳入的str通過調用父類的方法加到了該對象字串的後面,並將該對象返回了,這樣就可以連續的調用append()方法,並且保持對象的唯一性(String每次都會建立一個新的對象)。
在父類方法中使用String的getChars()方法,將我們傳入的str值添加的字元數組value中去。
public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); //將 str 添加到字元數組 value 中去 str.getChars(0, len, value, count); count += len; return this; }
[注]. 如想瞭解getChars()的工作原理,可以查看String的JDK源碼,這裡不再說明。
然後我們再來看一下最後的toString()方法
@Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
這個方法就會通過value這個字元數組裡面的值產生一個String返回過去。
上面我們我們可能對這個Builder有了一個初步的模糊的瞭解,接下來我就舉一個更加容易懂的例子,看一看這個Builder的好處。
設想一個UserInfo類,包含如下欄位
| 屬性 |
說明 |
是否必要 |
| userId |
主鍵,使用者ID |
是 |
| userName |
使用者姓名 |
是 |
| userPosition |
使用者職位(Java程式員一定為男性) |
否 |
| userSex |
性別 |
否 |
| userAge |
年齡 |
否 |
| userEmail |
郵箱 |
否 |
可知,在這個類中使用者ID,使用者姓名是必要的,其他的是非必要的。
在這種參數相對比較多的情況下,使用建構函式建立執行個體,在用戶端的代碼就會顯得比較麻煩,會分不清建構函式的哪一個參數對應哪一個值。
當然,我們可以為每個參數提供一個setter方法,但是這種方式也有兩種不足。
(1)因為構造過程被分到了幾次調用中,所以在構造過程中可能會處於不一致的狀態。類無法僅僅通過檢驗構造器參數的有效性來保證一致性。
[注]對於這句話,我個人的理解如下
比如上面的使用者類,我們規定如果職位為Java開發工程師,則性別一定是男性。
但是由於setter方法是分成兩部進行設定值,而且設定值的順序也是不一致的,所以就無法使兩個參數同時滿足上面的要求。
(2)對於一些final類是無法使用setter方法的。
那麼有沒有一種方法不僅能滿足重疊構造器的安全性,又能滿足像使用setter那樣的易讀性呢。
這時候我們的構建器(Builder)就閃耀登場了。使用Builder分成下面的三步: 使用所有的必要欄位(如上使用者ID,使用者姓名)產生一個Builder 在Builder上利用setter方法設定一些非必要的屬性(可以一次設定一個或多個,如使用者性別和職位的關聯,不滿足就拋異常)。 調用無參的builder()方法產生不可變的對象。(如StringBuilder中的為toString()方法)。
執行個體代碼如下
package com.blog.effective2;/** * @func 使用構建器(Builder) * Created by 張俊強~ on 2017/10/30. */public class UserInfo { private final String userId; //required private final String userName; //required private final String userPosition; private final String userSex; private final String userAge; private final String userEmail; public static class Builder { private final String userId; //required private final String userName; //required private String userPosition = ""; private String userSex = ""; private String userAge = ""; private String userEmail = ""; public Builder setUserEmail(String email) { userEmail = email; return this; } public Builder setUserSexAndPosition(String sex, String position) { //出現性別不是[男]的[Java程式員],則拋異常 if ((!"男".equals(sex)) && "Java程式員".equals(position)) { throw new RuntimeException("參數匹配異常"); } userSex = sex; userPosition = position; return this; } public Builder setUserAge(String age) { userAge = age; return this; } public Builder(String userId, String userName) { this.userId = userId; this.userName = userName; } //最後的 build 方法 public UserInfo build(){ return new UserInfo(this); } } public UserInfo(Builder builder) { this.userId=builder.userId; this.userName=builder.userName; this.userPosition=builder.userPosition; this.userAge = builder.userAge; this.userEmail = builder.userEmail; this.userSex = builder.userSex; } @Override public String toString() { return "UserInfo{" + "userId='" + userId + '\'' + ", userName='" + userName + '\'' + ", userPosition='" + userPosition + '\'' + ", userSex='" + userSex + '\'' + ", userAge='" + userAge + '\'' + ", userEmail='" + userEmail + '\'' + '}'; }}
有如下的測試代碼
public static void main(String[] args) { // 格式與 StringBuilder 是一致的. UserInfo userInfo=new UserInfo.Builder("001","張三").setUserAge("23") .setUserEmail("test@test.com") .setUserSexAndPosition("男","Java程式員").build(); System.out.println(userInfo.toString()); } //輸出結果 //UserInfo{userId='001', userName='張三', userPosition='Java程式員', userSex='男', userAge='23', userEmail='test@test.com'}
在這種模式下,由於在建立對象的同時需要建立相應的Builder對象,產生額外的花銷。
簡而言之,如果類的有多個參數時,可以考慮使用Builder模式(在該類中有很多參數是不必要的情況下,Builder模式表現的就更好)。
2017/10/30 13:05 於 福州大學.