Effective Java 第二章 創建和銷燬對象

第一條:考慮用靜態工廠方法代替構造器

  1. 優點
  • 它們相對於構造器有名稱,使得客戶端代碼更容易使用和閱讀。當一個類需要多個帶有相同簽名的構造器時,就用靜態工廠方法代替構造器,並且慎重地選擇名稱以便突出它們之間的區別。
  • 不必在每次調用它們的時候都創建一個新對象。 這使得不可變類可以使用預先構建好的實例,或者將構建好的實例緩存起來,進行重複利用,從而避免不必要的重複對象。如Boolean.valueOf(boolean),該方法從來不會創建新對象。如果程序經常請求創建相同的對象,並且創建對象的代價很高,利用這項技術可以極大地提升性能。
  • 靜態工廠方法可以返回原返回類型的任何子類型的對象。 利用這種方式,API可以返回對象,同時又不會使對象的類變成公有的。以這種方式隱藏實現類會使API變得非常簡潔。例如Java Collections Framework、java.util.EnumSet、Service Provider Framework。
  1. 啓發
    靜態工廠方法和公有構造器都各有用途,我們需要理解它們各自的長處。靜態工廠方法通常更加合適,因此切記第一反應就是提供公有的構造器,而不優先考慮靜態工廠。另外工廠方法的名字選取也很重要。通常的一些慣用名稱如下:
  • valueOf
  • of
  • getInstance
  • newInstance
  • getType
  • newType

第二條:遇到多個構造器參數時要考慮使用構造器

靜態工廠和構造器都不能很好的擴展到大量的可選參數。對於有大量可選參數的類,有以下幾種構造方式:

  • 重疊構造器模式:提供一個只有必要參數的構造器,第二個構造器有一個可選參數,第三個有兩個可選參數,以此類推,最後一個構造器包含所有可選參數。

缺點:當有許多參數的時候,客戶端代碼會很難寫,並且較難閱讀。

  • JavaBeans模式:調用一個無參構造器來創建對象,然後調用setter方法來設置每個必要的參數,以及每個相關的可選參數。

缺點:因爲構造過程中被分到了幾個調用中,在構造過程中JavaBean可能處於不一致的狀態。與此相關的另一個不足在於,JavaBeans模式阻止了把類做成不可變的可能,這就需要程序員付出額外的努力來確保它的現成安全。

  • Builder模式:既能保證像重疊構造器模式那樣的安全性,也能保證像JavaBeans模式那麼好的可讀性。這種模式不直接生成想要的對象,而是讓客戶端利用必要的參數調用Builder類的構造器(或者靜態工廠),得到一個builder對象。然後客戶端在builder對象上調用類似於setter的方法,來設置每個相關的可選參數。最後,客戶端調用無參的build方法來生成不可變的目標對象。這個builder是它構建的類的靜態成員類。

缺點:爲了創建目標對象,必須先創建它的構造器。雖然創建構造器的開銷在實踐中可能不那麼明顯,但是在某些十分注重性能的情況下,可能就成問題了。Builder模式還比重疊構造器模式更加冗長,因此它只在有很多參數的時候才使用,比如4個或者更多個參數。


第三條:用私有構造器或者枚舉類型強化Singleton屬性

常見單例模式實現方式的缺點:利用常規的單例模式僅僅在申明中加上“implements Serializable”是不夠的,爲了維護並保證Singleton,必須申明所有實例域都是瞬時(transient)的,並提供一個如下的readResolve方法。

//readResolve method to preserve singleton property
private Object readResolve() {
	//Return the one true Elvis and let the garbage collector take care of the Elvis impersonator.
	return INSTANCE;
}

否則,每次反序列化一個序列化的實例時,都會創建一個新的對象。所以在實現單例時,最優的做法是使用如下的方法:編寫一個包含單個元素的枚舉類型:

public enum Elvis {
	INSTANCE;
	public void leaveTheBuilding(){...}
}

這種方法在功能上與公有域方法相近,但是它更加簡潔,無償地提供了序列化機制,絕對防止多次實例話,即使是在面對複雜的序列化或者反射攻擊的時候。所以 單元素的枚舉類型是實現Singleton的最佳方法.


第四條:通過私有構造器強化不可實例化的能力

有時候我們需要編寫只含有靜態方法和靜態域的類,比如以java.lang.Math或者java.util.Arrays的方式,將基本類型的值或者數組類型上的相關方法組織起來。我們也可以通過java.util.Collections的方式,把實現特定接口的對象上的靜態方法(包括工廠方法)組織起來。最後,還可以利用這種類把final類上的方法組織起來,以取代擴展該類的做法。
這樣的工具類(utility class)不希望被實例化,實例對它沒有任何意義。但是在缺少顯示構造器的情況下,編譯器會自動爲該類提供一個公有的、無參的缺省構造器。因此,我們需要讓這個類不存在默認的無參構造器。通過爲類提供一個私有構造器,該類就不能被實例化了:

//Noninstantiable utility class
public class UtilityClass {
	//Suppress default constructor for noninstantiablity
	private UtilityClass() {
	//這裏的AssertioinError不是必須的,但是它可以避免不小心在類的內部調用構造器。他保證該類在任何情況下都不會被實例化。
		throw new AssertionError();
	}
}

副作用:它使得一個類不能被子類化。因爲所有的構造器都必須顯示或者隱式地調用超類(superclass)構造器,在這種情況下,子類就沒有可訪問的超類構造器可調用了。

第五條:避免創建不必要的對象

String s = new String(“stringette”)
Map.keySet 每次調用KeySet返回的都是同樣的Set實例。當其中一個返回對象發生變化的時候,所有其他的返回對象也要發生變化。

第六條:消除過期的對象引用

內存泄漏的常見來源:

  • 類自己管理內存
  • 緩存
  • 監聽器和其他回調

第七條:避免使用終結方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章