第一條:考慮靜態(tài)工廠方法代替構(gòu)造器
靜態(tài)工廠方法與構(gòu)造器相比的優(yōu)勢:
- 有名稱;
- 不必再每次調(diào)用他們的時(shí)候都創(chuàng)建一個(gè)新對象;
- 可以返回原返回類型的任何子類型的對象;
- 在創(chuàng)建參數(shù)化類型實(shí)例時(shí),代碼更加簡潔。
靜態(tài)工廠方法的缺點(diǎn):
- 類如果不含公有的或者受保護(hù)的構(gòu)造器,就不能被子類化;
- 它們與其他的靜態(tài)方法實(shí)際上沒有任何區(qū)別,無法標(biāo)記(通過命名規(guī)則來標(biāo)記)。
第二條:遇到多個(gè)構(gòu)造器參數(shù)時(shí)考慮用構(gòu)建器Builder
當(dāng)構(gòu)造器有多個(gè)參數(shù)時(shí)常用的方法:
重疊構(gòu)造(telescoping constructor):缺點(diǎn)在于客戶端代碼難以編寫且難閱讀。
JavaBean模式,調(diào)用一個(gè)無參構(gòu)造器來創(chuàng)建對象,然后調(diào)用setter方法來設(shè)置必要的參數(shù)。
缺點(diǎn)在于構(gòu)造過程被分到了多個(gè)調(diào)用中,可能存在不一致的狀態(tài),安全性能不能保證(需付出額外的努力);且對象不能是不可變的。Builder模式:安全性和可讀性并存,缺點(diǎn)在于開銷大,可能影響性能。
Builder模式:不直接生成對象,讓客戶端利用所有必要的參數(shù)調(diào)用構(gòu)造器,得到一個(gè)builder對象,然后客戶端在builder對象上調(diào)用類似于setter方法來設(shè)置每個(gè)相關(guān)的可選參數(shù),最后,調(diào)用無參的build方法來生成不可變的對象。
下面是一個(gè)Builder方法的實(shí)例
/**
* Created by laneruan on 2017/6/2.
* 遇到多個(gè)參數(shù)考慮使用構(gòu)建器Builder
* Java傳統(tǒng)的抽象工廠實(shí)現(xiàn)是Class類
* 優(yōu)點(diǎn):安全,直觀
* 缺點(diǎn):開銷略大,注重性能情況下需考慮。
*/
public class BuilderPattern {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static class Builder{
private final int servingSize;
private final int servings;
private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 0;
public Builder(int servingSize,int servings){
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val){
calories = val;
return this;
}
public Builder fat(int val){
fat = val;
return this;
}
public Builder sodium(int val){
sodium = val;
return this;
}
public Builder carbohydrate(int val){
carbohydrate = val;
return this;
}
public BuilderPattern build(){
return new BuilderPattern(this);
}
}
private BuilderPattern(Builder builder){
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
BuilderPattern cocaCola = new Builder(240,8).calories(100).sodium(35).calories(27).build();
}
第三條:用私有構(gòu)造器或枚舉類型來強(qiáng)化Singleton屬性
Singleton指僅僅被實(shí)例化一次的類,通常被用來代表那些本質(zhì)上唯一的系統(tǒng)組件。如:窗口管理器或文件系統(tǒng)。
實(shí)現(xiàn)Singleton的方法:
-
構(gòu)造器保持為私有,并導(dǎo)出公有的靜態(tài)成員,以便允許客戶端能夠訪問該類的唯一實(shí)例。公有域方法的主要好處在于:組成類的成員聲明很清楚地表明這個(gè)類是一個(gè)Singleton:公有的靜態(tài)域是final的。
public class Singleton { public static final Singleton INSTANCE = new Singleton(); private Singleton(){} public void leaveTheBuilding(){} }
私有構(gòu)造器只用了一次,用來實(shí)例化共有的靜態(tài)final域Singleton.INSTANCE。
-
第二種對應(yīng)于第一種方法公有的成員是個(gè)靜態(tài)工廠方法Singleton.getInstance()。
public class Singleton{ private static final Singleton INSTANCE = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return INSTANCE;} public void leaveTheBuilding(){} }
注意:前兩種方法實(shí)現(xiàn)的Singleton的類序列化的時(shí)候不僅僅加上
implements Serializable
,為了維護(hù)并保證Singleton,必須聲明所有的實(shí)例域都是瞬時(shí)的(transient)并且提供一個(gè)readResolve方法,否則每次反序列化一個(gè)序列化的實(shí)例時(shí),都會創(chuàng)建一個(gè)新的實(shí)例。//readResolve preserve singleton property private Object readResolve(){ return INSTANCE; }
-
第三種方法:編寫一個(gè)包含單個(gè)元素的枚舉類型(java 1.5后),更加地簡潔,無償?shù)靥峁┝诵蛄谢瘷C(jī)制。
public enum Singleton{ INSTANCE; public void leaveTheBuilding(){} }
第四條、通過私有構(gòu)造器強(qiáng)化不可實(shí)例化的能力
有時(shí)候需要編寫只包含靜態(tài)方法和靜態(tài)域的類,這樣的工具類utility class不希望被實(shí)例化,然而,在缺少顯式構(gòu)造器的情況下,編譯器會自動提供一個(gè)共有的,無參的缺省構(gòu)造器,因此,我們只需讓這個(gè)類包含私有構(gòu)造器,它就不能被實(shí)例化。由于顯式的構(gòu)造器是私有的,所以不能再該類的外部訪問它。
副作用:使一個(gè)類不能被子類化,因?yàn)樽宇悰]有可訪問的超類構(gòu)造器可調(diào)用。
第五條、避免創(chuàng)建不必要的對象
最好能重用對象而不是在每次需要的時(shí)候就創(chuàng)建一個(gè)相同功能的新對象。重用方式既快速又流行,如果對象是不可變的(immutable),它就始終可以被重用。
對于同時(shí)提供靜態(tài)工廠方法和構(gòu)造器的不可變類,通常可以使用靜態(tài)工廠方法而不是構(gòu)造器,以避免創(chuàng)建不必要的對象。
也可以重用那些已知不會被修改的可變對象,可以用一個(gè)靜態(tài)的初始化器(initializer)避免調(diào)用方法就創(chuàng)建對象效率低下的情況。
適配器adapter(有時(shí)候也叫作視圖(view)):它把功能委托給一個(gè)后備對象,從而為后備對象提供一個(gè)可以替代的接口。由于適配器除了后備對象之外,沒有其他的狀態(tài)信息,所以針對某個(gè)給定對象的特定適配器而言,它不需要創(chuàng)建多個(gè)適配器實(shí)例。比如Map接口的keySet方法返回該Map對象的Set視圖,其中包含該Map中所有的鍵。粗看起來,仿佛每次調(diào)用keySet都應(yīng)該創(chuàng)建一個(gè)新的Set實(shí)例,但是對于給定的Map對象,所有返回的對象在功能上是相同的:當(dāng)其中一個(gè)返回對象發(fā)生變化的時(shí)候,所以其他的對象也要發(fā)生變化,因?yàn)樗鼈兪怯赏粋€(gè)Map實(shí)例支撐的。
自動裝箱(autoboxing):它允許程序員將基本類型和裝箱基本類型混用,按照需要自動裝箱和拆箱。這樣使得兩者之間的差別變得模糊起來,但是,兩者在性能上具有明顯的區(qū)別。要優(yōu)先使用基本類型而不是裝箱類型(如long和Long在多重循環(huán)中開銷不同)
靜態(tài)初始化器用法的實(shí)例:
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
/**
* Created by laneruan on 2017/6/5.
*/
public class staticInitializer {
//this is a person class
private final Date birthDate = new Date();
//other fields,methods and constructor omo=itted
//Don't do this!
public boolean isBabyBoomer(){
//每次被調(diào)用時(shí),會新建一個(gè)Calendar,一個(gè)TimeZone和兩個(gè)Date類,這是不必要的
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946,Calendar.JANUARY,1,0,0,0);
Date boomStart = gmtCal.getTime();
gmtCal.set(1965,Calendar.JANUARY,1,0,0,0);
Date boomEnd = gmtCal.getTime();
return birthDate.compareTo(boomStart) >= 0 &&
birthDate.compareTo(boomEnd)<0;
}
//改進(jìn)后的類只需在初始化的時(shí)候創(chuàng)建各實(shí)例一次,而不是在每次調(diào)用方法的時(shí)候都創(chuàng)建這些實(shí)例。
//而且將對象從局部變量改為final靜態(tài)域,使得代碼更易于理解。
private static final Date BOOM_START;
private static final Date BOOM_END;
static{
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946,Calendar.JANUARY,1,0,0,0);
BOOM_START = gmtCal.getTime();
gmtCal.set(1965,Calendar.JANUARY,1,0,0,0);
BOOM_END = gmtCal.getTime();
}
public boolean isBabyBoomer2(){
return birthDate.compareTo(BOOM_START) >= 0 &&
birthDate.compareTo(BOOM_END)<0;
}
}
第六條、消除過期的對象引用
-
內(nèi)存泄漏的第一個(gè)常見來源:過期引用。
如果一個(gè)棧先增長,然后再收縮,那么從棧中彈出來的對象將不會被當(dāng)作垃圾回收,即使使用棧的程序不再引用這些對象,它們也不會被回收。因?yàn)闂?nèi)部維護(hù)著對這些對象的過期引用(obsolete reference)。如果一個(gè)對象的引用被無意識地保留起來了,那么垃圾回收機(jī)制將不會處理這個(gè)對象,而且也不會處理這個(gè)對象所引用的其他對象。即使只有少量的幾個(gè)對象被無意識地保留下來,也會有許許多多的對象被排除在GC之外。
修復(fù)這種方法很簡單:一旦對象引用已經(jīng)過期,只需清空這些引用即可(這只是一種例外,而不是規(guī)范的行為)。
清除過期引用的另一個(gè)好處是如果以后又被錯(cuò)誤地解除引用,程序就會立即拋出NullPointerException,而不是默默地錯(cuò)誤運(yùn)行下去。何時(shí)應(yīng)該清空引用呢?因?yàn)镾tack類自己管理內(nèi)存,數(shù)據(jù)池包含了elements數(shù)組的元素,數(shù)組活動區(qū)域中的元素是已分配的,而其余部分的元素則是自由的,但是垃圾回收器并不知道這一點(diǎn),elements數(shù)組中的所有對象引用都同等有效,所有一旦數(shù)組元素變成了非活動部分的一部分,程序員就手動清除這些數(shù)組元素。只要類是自己管理內(nèi)存,就應(yīng)該警惕內(nèi)存泄露的問題。
-
內(nèi)存泄露的另一個(gè)常見來源是緩存。
一旦把對象引用放入緩存中,很容易遺忘。
解決方案:當(dāng)緩存過期后它們就會自動被清除。 -
內(nèi)存泄露的第三個(gè)常見來源是監(jiān)聽器和其他回調(diào)。
如果你實(shí)現(xiàn)了一個(gè)API,客戶端在這個(gè)API中注冊回調(diào),卻沒有顯式地取消注冊,那么除非你采取某些行動,否則它們就會集聚,確保回調(diào)立即被當(dāng)做垃圾回收的最佳方法是只保存它們的弱引用,例如,只將它們保存成WeakHashMap中的鍵
第七條、避免使用終結(jié)方法finalizer
為何避免使用終結(jié)方法:
finalizer通常是不可預(yù)測的,也是危險(xiǎn)的,一般情況下是不必要的。可能會導(dǎo)致行為不穩(wěn)定,降低性能,以及可移植的問題。
終結(jié)方法的主要缺點(diǎn)在于不能保證會被及時(shí)地執(zhí)行。從一個(gè)對象變得不可達(dá)開始到執(zhí)行它的終結(jié)方法,所花費(fèi)的時(shí)長是任意的。Java的語言規(guī)范不僅不保證終結(jié)方法會被及時(shí)地執(zhí)行,而且根本就不能保證它們會被執(zhí)行!
提供一個(gè)顯式的終結(jié)方法:如InputStream、OutputStream和java.sql.Connection上的close方法。顯式的終結(jié)方法通常與try-finally結(jié)構(gòu)結(jié)合使用,確保及時(shí)終止。
終結(jié)方法的優(yōu)點(diǎn):
第一種用途是充當(dāng)安全網(wǎng),遲一點(diǎn)釋放關(guān)鍵資源總比永遠(yuǎn)不釋放要好。
第二個(gè)合理的用途是與對象的本地對等體(native peer)有關(guān)。本地對等體是一個(gè)本地對象,普通對象通過本地方法委托給一個(gè)本地對象,因?yàn)楸镜貙Φ润w不是一個(gè)普通的對象,所以垃圾回收器不會知道他,當(dāng)他的java對等體被回收,它不會被回收。當(dāng)本地對等體并不具有關(guān)鍵資源的前提下,終結(jié)方法正是執(zhí)行這種任務(wù)的最合適工具。