在 Java 中有多種方式能夠建立對象,總結起來主要有下面的 4 種方式:java
正常建立。經過 new 操做符程序員
反射建立。調用 Class 或 java.lang.reflect.Constructor 的 newInstance()方法數組
克隆建立。調用現有對象的 clone()方法安全
發序列化。調用 java.io.ObjectInputStream 的 getObject()方法反序列化多線程
Java 對象的建立方式是其語法明確規定,用戶不可能從外部改變的。本文仍然要使用上面函數
的方式來建立對象,因此本文只能說是構建對象,而非建立對象也。ui
假設有這樣一個場景,如今要構建一個大型的對象,這個對象包含許多個參數的對象,有this
些參數有些是必填的,有些則是選填的。那麼如何構建優雅、安全地構建這個對象呢?.net
單一構造函數線程
一般,咱們第一反應能想到的就是單一構造函數方式。直接 new 的方式構建,經過構造函
數來傳遞參數,見下面的代碼:
/***
* 單一構造函數
*/
public class Person {
// 姓名(必填)
private String name;
// 年齡(必填)
private int age;
// 身高(選填)
private int height;
// 畢業學校(選填)
private String school;
// 愛好(選填)
private String hobby; public Person(String name, int age, int height, String school, String hobby) { this.name = name; this.age = age; this.height = height; this.school = school; this.hobby = hobby; } }
上面的構建方式有下面的缺點:
有些參數是能夠選填的(如 height, school),在構建 Person 的時候必需要傳入可能並不需
要的參數。
如今上面才 5 個參數,構造函數就已經很是長了。若是是 20 個參數,構造函數均可以直接
上天了!
構建的這樣的對象很是容易出錯。客戶端必需要對照 Javadoc 或者參數名來說實參傳入對
應的位置。若是參數都是 String 類型的,一旦傳錯參數,編譯是不會報錯的,可是運行結
果倒是錯誤的。
多構造函數
對於第 1 個問題,咱們能夠經過構造函數重載來解決。見下面的代碼:
/***
* 多構造函數
*/
public class Person {
// 姓名(必填)
private String name;
// 年齡(必填)
private int age;
// 身高(選填)
private int height;
// 畢業學校(選填)
private String school;
// 愛好(選填)
private String hobby; public Person(String name, int age) { this.name = name; this.age = age; } public Person(String name, int age, int height) { this.name = name; this.age = age; this.height = height; } public Person(String name, int age, int height, String school) { this.name = name; this.age = age; this.height = height; this.school = school; } public Person(String name, int age, String hobby, String school) { this.name = name; this.age = age; this.hobby = hobby; this.school = school; } }
上面的方式確實能在必定程度上下降構造函數的長度,可是卻有下面的缺陷:
致使類過長。這種方式會使得 Person 類的構造函數成階乘級增加。按理來講,應該要寫的
構造函數數是可選成員變量的組合數(實際並無這麼多,緣由見第 2 點)。若是讓我調用
這樣的類,絕對會在內心默唸 xx!!
有些參數組合沒法重構。由於 Java 中重載是有限制的,相同方法簽名的方法不能構成重
載,編譯時沒法經過。譬如包含(name, age, school)和(name, age, hobby)的構造函數是不
能重載的,由於 shcool 和 hobby 同爲 String 類型。Java 只認變量的類型,管你變量是什麼
含義呢。 (看臉的社會唉)
JavaBean 方式
上面的方法不行,莫急!還有法寶——JavaBean。一個對象的構建經過多個方法來完成。
直接見下面的代碼:
public class Person {
// 姓名(必填)
private String name;
// 年齡(必填)
private int age;
// 身高(選填)
private int height;
// 畢業學校(選填)
private String school;
// 愛好(選填)
private String hobby; public Person(String name, int age) { this.name = name; this.age = age; } public void setHeight(int height) { this.height = height; } public void setSchool(String school) { this.school = school; } public void setHobby(String hobby) { this.hobby = hobby; } }
客戶端使用這個對象的代碼以下:
public class Client { public static void main(String[] args) { Person person = new Person("james", 12); person.setHeight(170); person.setHobby("reading"); person.setSchool("xxx university"); } }
這樣看起來完美的解決了 Person 對象構建的問題,使用起來很是優雅便捷。確實,在單一
線程的環境中這確實是一個很是好的構建對象的方法,可是若是是在多線程環境中仍有其
致命缺陷。在多線程環境中,這個對象不能安全地被構建,由於它不是不可變對象。一旦
Person 對象被構建,咱們隨時可經過 setXXX()方法改變對象的內部狀態。假設有一個線程
正在執行與 Person 對象相關的業務方法,另一個線程改變了其內部狀態,這樣獲得莫名
其妙的結果。因爲線程運行的無規律性,使得這問題有可能不能重現,這個時候真的就只
能哭了。(程序員真苦逼。。。)
Builder 方式
爲了完美地解決這個問題,下面引出本文中的主角(等等等等!)。咱們使用構建器
(Builder)來優雅、安全地構建 Person 對象。廢話不說,直接代碼:
/**
* 待構建的對象。該對象的特色:
* <ol>
* <li>須要用戶手動的傳入多個參數,而且有多個參數是可選的、順序隨意</li>
* <li>該對象是不可變的(所謂不可變,就是指對象一旦建立完成,其內部狀態不可變,
更通俗的說是其成員變量不可改變)。
* 不可變對象本質上是線程安全的。</li>
* <li>對象所屬的類不是爲了繼承而設計的。</li>
* </ol>
* 知足上面特色的對象的構建但是使用下面的 Build 方式構建。這樣構建對象有下面的好
處:
* <ol>
* <li>不須要寫多個構造函數,使得對象的建立更加便捷</li>
* <li>建立對象的過程是線程安全的</li>
* </ol> * @author xialei * @date 2015-5-2 */ public class Person {
// 姓名(必填),final 修飾 name 一旦被初始化就不能再改變,保證了對象的不可變
性。
private final String name;
// 年齡(必填)
private final int age;
// 身高(選填)
private final int height;
// 畢業學校(選填)
private final String school;
// 愛好(選填)
private final String hobby;
/**
* 這個私有構造函數的做用:
* <ol>
* <li>成員變量的初始化。final 類型的變量必須進行初始化,不然沒法編譯成功</li>
* <li>私有構造函數可以保證該對象沒法從外部建立,而且 Person 類沒法被繼承</li>
* </ol> */ private Person(String name, int age, int height, String school, String hobby) { this.name = name; this.age = age; this.height = height; this.school = school; this.hobby = hobby; } /**
* 要執行的動做
*/ public void doSomething() { // TODO do what you want!! } /**
* 構建器。爲何 Builder 是內部靜態類?
* <ol>
* <li>必須是 Person 的內部類。不然,因爲 Person 的構造函數私有,不能經過 new 的
方式建立 Person 對象</li>
* <li>必須是靜態類。因爲 Person 對象沒法從外部建立,若是不是靜態類,則外部無
法引用 Builder 對象。</li>
* </ol>
* <b>注意</b>:Builder 內部成員變量要與 Person 的成員變量保持一致。
* @author xialei
*
*/
public static class Builder {
// 姓名(必填)。注意:這裏不能是 final 的
private String name;
// 年齡(必填)
private int age;
// 身高(選填)
private int height;
// 畢業學校(選填)
private String school;
// 愛好(選填)
private String hobby; public Builder(String name, int age) { this.name = name; this.age = age; } public Builder setHeight(int height) { this.height = height; return this; } public Builder setSchool(String school) { this.school = school; return this; } public Builder setHobby(String hobby) { this.hobby = hobby; return this; } /**
* 構建對象
* @return 返回待構建的對象自己
*/ public Person build() { return new Person(name, age, height, school, hobby); } } }
客戶端構建對象的方式見下面的代碼:
/**
* 使用 Person 對象的客戶端
* @author xialei * @date 2015-5-2 */ public class Client { public static void main(String[] args) { /*
* 經過鏈式調用的方式建立 Person 對象,很是優雅!
*/ Person person = new Person.Builder("james", 12) .setHeight(170) .setHobby("reading") .build(); person.doSomething(); } }
若是不想看代碼,可看下面對於上面代碼的總結:
經過 private Person(..)使得 Person 類不可被繼承
經過將 Person 類的成員變量設置爲 final 類型,使得其不可變
經過 Person 內部的 static Builder 類來構建 Person 對象
經過將 Builder 類內部的 setXXX()方法返回 Builder 類型自己,實現鏈式調用構建 Person 對
象
總結
至此,咱們就相對完美地解決這一類型的對象建立問題!下面來總結一下本文的重點。待
建立的對象特色:
須要用戶手動的傳入多個參數,而且有多個參數是可選的、順序任意
對象不可變
對象所屬的類不是爲了繼承而設計的。即類不能被繼承
依次使用的對象構建方法:
單一構造函數
多構造函數
JavaBean 方式
Builder 方式
最終,經過比較得出 Builder 方法最爲合適的解決。