如何更好的建立Java對象

靜態工廠

除了使用構造函數建立對象外,還可使用靜態工廠來建立對象,JDK中大量使用了這種技巧,例如:前端

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}

靜態工廠的優點

使用靜態工程有幾大優點,具體以下:java

靜態工廠有名稱編程

當一個類須要多個帶有相同簽名的構造器時,就用靜態工廠方法代替構造函數,而且慎重地選擇名稱以便突出他們以前的區別。json

相比之下,構造函數只能有一個名稱,若是要接受不一樣的參數,須要進行重載,面對這樣的API,用戶很難記住還用哪一個構造函數去建立一個對象,也常常調用錯誤的構造器,若是在遇到沒有參考文檔的狀況,每每不知從何入手。設計模式

沒必要每次都建立一個新的對象緩存

靜態方法能夠重複調用,返回同一個對象,這種類被稱爲實例受控的類(instance-controlled)。經過這種技術衍生出來的一個設計模式就是單例模式(Singleton)。安全

關於單例模式的實現能夠參考:http://www.javashuo.com/article/p-dwkzzxqk-da.html函數

能夠不返回對象自己的類型工具

這句話比較繞,但解釋起來很簡單,意思是,靜態工廠能夠返回要返回的對象的父類或接口。性能

也就是說,能夠要求客戶端調用者使用接口來引用(接收)靜態工廠返回的類型,而不是經過它的實現類來引用被返回的對象,這是一個好習慣,也就是咱們常說的,面向接口編程。

雖然構造函數也能夠作到這一點,可是,靜態工廠能夠根據接收的參數返回不一樣的類型,這點是構造函數作不到的,好比:

public static ColorInterface createInstance(int type) {
    if (type == 0) {
        return new Red();
    } else {
        return new Green();
    }
}

並且,若是有新的類型須要返回,也能夠添加到靜態工廠中。

建立對象的語法更簡潔

在調用泛型對象時,調用構造函數一般須要編寫兩次泛型類型:

Map<String, List<String>> m = new HashMap<String, List<String>>();

這種方式在JDK1.7中獲得類優化,加入了類型推導,在1.7中不須要再編寫兩次泛型類型:

// jdk1.7+
Map<String, User> m = new HashMap<>();

也可使用靜態工廠讓其建立變得簡單,而且與JDK版本無關:

Map<String, List<String>> m = Factory.newInstance();

靜態工廠的不足

與其餘靜態方法沒有任何區別

靜態方法沒有像構造函數那樣在API中明確標識,所以,若是要用靜態工廠實例化一個類,須要特地去了解API文檔,可是使用一些命名規範,能夠彌補這個劣勢,這些規範以下:

valueOf:該方法返回的實例與它的參數具備相同的值,目的是作類型轉換;

of:是valueOf的一種更簡潔的替代;

getInstance:返回單例對象;

newInstance/createInstance:建立一個實例,至關於new一個對象;

構建器

靜態工廠與構造函數有一個共同的侷限性:建立對象時,不能很好地擴展大量的可選參數。

舉例來講,在建立一個有20個域(字段)的對象,其中有3個域是建立時必需要初始化的,另外17個是選填初始化的,那麼就要進行大量的構造函數重載,提供一個3個參數(3個必填域)的構造函數,雖然重載一個4個參數(3個必填域,1個可選域),隨後提供一個5個參數的、6個參數的……

重載構造函數雖然可行,可是類的代碼很難寫,而且難以閱讀與維護。

由此需求,Builder設計模式就誕生了。它不直接生成想要的對象,而是讓API使用者利用全部的必填參數來建立一個建造者對象。而後利用建造者對象的setter方法去初始化選填參數。最後調用建造者的build方法來生成對象。

實戰經驗

不少Web工程都建立了一個返回值對象,用於向前端返回信息,其結構以下:

public class ResponseBean() {
    //  狀態碼,例如:0表明成功,1表明異常
    private int code;
    //  要返回的信息
    private String message;
    //  要返回的數據
    private Object data;
}

上面的類中,在建立ResponseBean對象時,只有code是必填的,message和data都是選填的,這種狀況就可使用Builder模式進行對象的建立(例子中只有3個字段,考慮字段更多的狀況)。

分享一個實現,具體以下(能夠將WebAppResult設計成泛型類):

package cn.yesway.pms.entity;

import cn.yesway.pms.enums.ResultStatus;

/**
 * Web應用的響應數據實體對象。<br>
 * 包含了與前端約定的屬性。
 */
public class WebAppResult {

	// 響應狀態
	// 不是http的響應狀態,是業務系統的響應狀態
	// 業務經過返回Success
	// 業務不經過(例如,登陸失敗等)返回Fail
	private final ResultStatus status;
	// 發送給前端的信息
	private final String msg;
	// 發送給前端的json數據
	private final Object data;

	private WebAppResult(Builder builder) {
		this.status = builder.status;
		this.msg = builder.msg;
		this.data = builder.data;
	}

	/**
	 * WebAppResult的構造器。
	 */
	public static class Builder {

		private final ResultStatus status;
		private String msg;
		private Object data;

		public Builder(ResultStatus status) {
			this.status = status;
		}

		public Builder msg(String msg) {
			this.msg = msg;
			return this;
		}

		public Builder data(Object data) {
			this.data = data;
			return this;
		}

		public WebAppResult build() {
			return new WebAppResult(this);
		}
	}

	/**
	 * @return 得到響應狀態
	 */
	public ResultStatus getStatus() {
		return status;
	}

	/**
	 * @return 得到響應消息
	 */
	public String getMsg() {
		return msg;
	}

	/**
	 * @return 得到響應數據
	 */
	public Object getData() {
		return data;
	}

}

建立WebAppResult實體以下:

// 沒有data
return new WebAppResult.Builder(ResultStatus.SUCCESS).msg("操做成功").build();

這些編寫,可讓代碼很容易的編寫,並且易於閱讀。

條件檢測

將參數從builder拷貝到對象後,對象域能夠對這些參數進行檢查,若是失敗能夠拋出IllegalStateException異常。在setter時也能夠加入條件約束,失敗拋出IllegalArgumentException。在set階段就會檢查條件,而不是等到調用build方法時。

存在的不足

爲了建立對象,必須建立Builder。若是十分注重性能,可能就會形成問題。

通常在須要用不少參數去構造一個對象時,好比4個參數或更多,使用Builder更爲合適。

簡而言之,若是類的構造函數或者靜態方法中具備多個參數,設計這種類時,Builder模式就是一種不錯的選擇。特別是大多數參數都是可選的時,相比構造函數和靜態工廠,Builder模式更易於閱讀和編寫。

建立不可實例化的類

有時候可能須要編寫只包含靜態方法和靜態域的類。這些類的名聲很很差,由於有些人在面嚮對象語言中濫用這樣的類。儘管如此,他們也確實有他們特有的用處。例如java.lang.Math和java.util.Arrays。

這樣的工具類不但願被實例化,實例對它沒有意義。而後在默認狀況下,編譯器會提供一個公有的、無參數的構造函數。咱們能夠將類的構造函數顯示實現爲private的,來防止類的實例化:

public class Util {
    private Util() {
        throw new AssertionError();
    }
}

AssertionError不是必須的,它是防止在Util類的內部,在任何狀況下都不會實例化該類。

避免建立沒必要要的對象

通常來講,最好能重用對象而不是在每次須要的時候就建立一個相同功能的對象。重用方式既快速,又流行。若是對象是不可變的(immutable),它就始終能夠被重用。

對用同時提供類靜態工廠和構造函數的不可變類,一般可使用靜態工廠而不是構造函數,以免建立沒必要要的對象,例如:

// 推薦
Boolean.valueOf(String);
// 不推薦
Boolean(String)

有些時候,對象並不必定是不可變對象,這時候能夠考慮適配器模式(adapter),有時也叫視圖模式(view)。

它把功能委託給一個後備對象(backing object),從而爲後備對象提供一個能夠替代的接口。因爲適配器除了後備對象以外,沒有其餘的狀態信息,因此針對某個給定對象的特定適配器而言,它不須要建立多個適配器實例,關於適配器模式的信息能夠參考:http://www.javashuo.com/article/p-sfpkgdol-dz.html

避免建立沒必要要的,不等於儘可能避免建立對象,因爲小對象的構造器只作了少許工做,它的建立和回收是特別廉價的,特別在現代的JVM上更是如此。經過建立對象提高程序的清晰性、簡潔性和功能性是件好事。關於對象的分配和回收,能夠參考:http://www.javashuo.com/article/p-rxxjxtjk-o.html

過時的對象引用

過時引用很容易形成內存泄漏,在支持垃圾回收的語言中,內存泄漏是很隱蔽的,這類問題的修復方法很簡單,一旦對象引用已通過期,只須要清空這些引用便可。

清空對象引用應該是一種例外,而不是一種規範行爲。

通常而言有三個常見問題會形成內存泄漏:

第一個常見問題,只要類的本身管理內存的,就要警戒內存泄漏,解決方法是一旦對象被釋放,就清空這個對象內包含的任何引用;

第二個常見問題,來自於緩存的內存泄漏,解決方法是按期清除過時的緩存;

第三個常見問題,來自於監聽器和回調器,解決方法是確保手動取消註冊,並在監聽器中將註冊的對象保存在弱引用(week reference)中;

避免使用finalizer

終結方法通常狀況下是沒必要要的。Java中能夠採用try-finally來完成相似的工做。

終結方法不能確保被及時的調用,並且在不一樣的JVM實現中,調用終結方法的時間點大不相同,並且終結方法的線程優先級不高。Java語言規範不只不保證總結方法會被及時的調用,並且根本就不保證他們會被執行。

結論爲:不該該依賴終結方法來更新重要的狀態。

避免使用終結方法,只須要提供一個顯示的終止方法(自定義),並要求客戶端在每一個實例再也不有用時調用這個方法,例如InputStream、OutputStream、Connection等都提供類close()方法。他們一般是配合try-finally結構結合起來使用的。

終結方法的用途有兩種:

第一種,當客戶端忘記調用上面提到的顯示終止方法時,充當「安全網」(safety net),使用這種方法,強烈建議在終結方法中,發現資源未終止,必須在日誌中記錄一條警告。

第二種,在使用native有關的操做時,JVM不知道須要回收這些native object,在native object不擁有關鍵資源的前提下,在終結方法中回收它。

注意,若是子列覆蓋類父類的終結方法,須要手動調用父類的終結方法,不然父類的終結方法永遠不會執行。

再次強調,除了以上兩種狀況,不然請不要使用終結方法。

相關文章
相關標籤/搜索