新聞中心
其實這個問題很多人都進行過解答,也有很多小伙伴在Java四大名著之一的《Effective Java》中的第1~5小節(jié)了解過,不過我還是想結合自己的理解對這個問題進行總結和歸納,談談為什么會最終選擇構建器來實現我們的目的。

在 Java 中有多種方式可以創(chuàng)建對象,總結起來主要有下面的 4 種方式:
正常創(chuàng)建:通過 new 操作符
反射創(chuàng)建:調用 Class 或 java.lang.reflect.Constructor 的 newInstance()方法
克隆創(chuàng)建:調用現有對象的 clone()方法
發(fā)序列化:調用 java.io.ObjectInputStream 的 getObject()方法反序列化
Java 對象的創(chuàng)建方式是其語法明確規(guī)定,用戶不可能從外部改變的。本文仍然要使用上面的方式來創(chuàng)建對象,所以本文只能說是構建對象,而非創(chuàng)建對象也。
假設有這樣一個場景,現在要構建一個大型的對象,這個對象包含許多個參數的對象,有些參數有些是必填的,有些則是選填的。那么如何構建優(yōu)雅、安全地構建這個對象呢?
01 單一構造函數
通常,我們第一反應能想到的就是單一構造函數方式。直接 new 的方式構建,通過構造函數來傳遞參數,見下面的代碼:
- /***
- * 單一構造函數
- */
- public class Person {
- // 姓名(必填)
- private String name;
- // 年齡(必填)
- private int age;
- // 身高(選填)
- private int height;
- // 畢業(yè)學校(選填)
- 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 類型的,一旦傳錯參數,編譯是不會報錯的,但是運行結果卻是錯誤的。
02 多構造函數
對于第 1 個問題,我們可以通過構造函數重載來解決。見下面的代碼:
- /***
- * 多構造函數
- */
- public class Person {
- // 姓名(必填)
- private String name;
- // 年齡(必填)
- private int age;
- // 身高(選填)
- private int height;
- // 畢業(yè)學校(選填)
- 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 只認變量的類型,管你變量是什么含義呢。
03 JavaBean方式
上面的方法不行,莫急!還有法寶——JavaBean。一個對象的構建通過多個方法來完成。直接見下面的代碼:
- public class Person {
- // 姓名(必填)
- private String name;
- // 年齡(必填)
- private int age;
- // 身高(選填)
- private int height;
- // 畢業(yè)學校(選填)
- 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 對象構建的問題,使用起來非常優(yōu)雅便捷。確實,在單一線程的環(huán)境中這確實是一個非常好的構建對象的方法,但是如果是在多線程環(huán)境中仍有其致命缺陷。在多線程環(huán)境中,這個對象不能安全地被構建,因為它不是不可變對象。一旦Person 對象被構建,我們隨時可通過 setXXX()方法改變對象的內部狀態(tài)。假設有一個線程正在執(zhí)行與 Person 對象相關的業(yè)務方法,另外一個線程改變了其內部狀態(tài),這樣得到莫名其妙的結果。由于線程運行的無規(guī)律性,使得這問題有可能不能重現,這個時候真的就只能哭了。
04 Builder方式
為了完美地解決這個問題,下面引出本文中的主角(等等等等?。N覀兪褂脴嫿ㄆ鳎˙uilder)來優(yōu)雅、安全地構建 Person 對象。廢話不說,直接代碼:
- /**
- * 待構建的對象。該對象的特點:
- *
- *
- 需要用戶手動的傳入多個參數,并且有多個參數是可選的、順序隨意
- *
- 該對象是不可變的(所謂不可變,就是指對象一旦創(chuàng)建完成,其內部狀態(tài)不可變,
- 更通俗的說是其成員變量不可改變)。* 不可變對象本質上是線程安全的。
- *
- 對象所屬的類不是為了繼承而設計的。
- *
客戶端構建對象的方式見下面的代碼:
- /**
- * 使用 Person 對象的客戶端
- * @author xiaoyu
- * @date 2020-10-25
- */
- public class Client {
- public static void main(String[] args) {
- /*
- * 通過鏈式調用的方式創(chuàng)建 Person 對象,非常優(yōu)雅!
- */
- 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 對 象
總結
至此,我們就相對完美地解決這一類型的對象創(chuàng)建問題!下面來總結一下本文的重點。待創(chuàng)建的對象特點:
需要用戶手動的傳入多個參數,并且有多個參數是可選的、順序任意
對象不可變
對象所屬的類不是為了繼承而設計的。即類不能被繼承
依次使用的對象構建方法:
單一構造函數
多構造函數
JavaBean 方式
Builder 方式
最終,通過比較得出 Builder 方法最為合適的解決。
文章名稱:如何在Java中創(chuàng)建優(yōu)雅的對象來提升程序性能
本文鏈接:http://m.5511xx.com/article/cdocoos.html


咨詢
建站咨詢
