這本書對於java程序員的意義就如《Effective C++》對於C++程序員的意義一樣,我想是每個Java愛好者的必讀書之一了,最近在啃這本書,一些學習筆記希望能對大家有所幫助。
一。創建和銷毀對象
第一條:考慮用靜態工廠方法代替構造函數
實例代碼 :Boolean類中的valueOf()方法
public static Boolean valueOf(boolean b)
{
return (b?Boolean.TRUE:Boolean.FALSE);
}
優點:
1。和構造函數不同,靜態工廠方法有自己的名字。如果存在著多種版本的構造函數,有的僅僅是參數順便的不同,此時你應該考慮用靜態工廠方法。
2。靜態工廠方法不要求一定要創建對象。可使用預先構造好的對象。例如Boolean.valueOf()方法就從不創建對象。在需要頻繁創建對象,並且創建對象成本較高的情況下,你應該考慮采用靜態工廠方法
3。與構造函數不同,靜態工廠方法可以返回一個原返回類型的子類型的對象。這方面的最好的例子就是Collections Framework。Collections Framework有20個實用的集合接口實現,這些實現大多數是通過一個不可實例子化的Java.utl.Collections中的靜態工廠方法導出的。
缺點:
1。類如果不含有公有或者受保護的構造函數,就不能被繼承。某種意義上這也限制了繼承的濫用
2。靜態工廠方法和其他靜態方法一樣,一般要在API文檔中作出特別的說明。在沒有強烈的需要下,你還是應該使用規范的構造函數。
第2條:使用私有構造函數強化singleton屬性
所謂singleton是指這樣的類,它只能被實例化一次/(也就是單例模式),有兩種方式,如下:
1。提供一個靜態常量
public class Example{
public static final Example INSTANCE=new Example();
private Example(){ //構造函數為私有
...}
....
}
2。使用靜態工廠方法
public class Example{
private static final Example INSTANCE=new Example();//改為私有
private Example(){ //構造函數為私有
...}
public static Example getInstance(){
return INSTANCE;
}
....
}
第一種方法在性能上可能更好,第2種方法提供了更大的靈活性,你可以決定是否做成singleton。要使一個singleton的類變成可序列化的,僅僅實現Serializable接口是不夠,還必須提供一個readResolve()方法,否則會產生一個新的實例。違背了singleton的本意
private Object readResolve() throws ObjectStreamException{
return INSTANCE;
}
第3條。通過私有構造函數強化不可實例能力
也就是不使某個類不能產生任何對象。或者你要說寫成抽象類不就可以了?NO,抽象類可以被實現,其子類也可以被實現。我們要的是絕對不能被實例化的類,這種類一般只有一些靜態變量和靜態方法,只是作為工具類使用,如Java.utl.Arrays。要做到這一點只要包含一個私有的顯式構造函數。這樣同時也保證了這個類不能被繼承,因為子類無法訪問父類的構造函數。
第4條:避免重復創建對象
如果一個對象是非可變的,那麼它總可以被重用,而不是再去創建一個對象。例如
String s=new String("denny");
裡面的"denny"本身就是一個實例。而這句話每次又重新創建一個同樣的實例。這完全是沒有必要的,如果在一個頻繁調用的方法中使用這樣的語句,性能上會有很大影響。應該用
String s="denny";來代替上面的語句。一個常用的方法是把重復需要用到的對象做成類的私有的靜態常量(當然,要保證這些變量在創建以後不再改變),用一個static塊包含他們。另外,不要以為創建對象是代價非常昂貴,相反,一些小對象的構造函數往往只做很少的工作,所以小對象的創建是非常廉價的,只有重量級的對象(如數據庫連接)才需要采用對象池來重用對象。
第5條:消除過期引用
“內存洩露”!什麼,我有沒有聽錯,Java也有“內存洩露”。是的,那不是C++的專利。看下面的例子
public Class Stack{
private Object[] elements;
private in size=0;
public Stack(int initialCapacity){
this.elements=new Object[initialCapacity];
}
public Object pop()
{
if(size==0)
throw new EmptyStackException();
return elements[--size];
}
....
} 這個程序並沒有很明顯的錯誤,但是隨著不斷增加的內存占用,程序的性能的降低會逐漸顯現。原因在於這個棧收縮的時候,從棧中彈出的對象並不會被當作垃圾回收,這是因為棧內部維持著這些對象的過期引用,也就是永遠也不會再被解除的引用,應該把pop操作修改下:
public Object pop()
{
if(size==0)
throw new EmptyStackException();
Object result=elements[--size];
elements[size]==null; //把引用設為null
return result;
}
自己管理內存的類一般都存在著這樣的問題,必須時刻警惕。內存洩露的另一個來源就是緩存了,你緩存了一個對象,卻忘記了去釋放。內存洩露問題可以通過專門的工具來檢測。
第6條:避免使用終結函數(finalize())
想起一次在CSDN論壇上,有人問什麼時候該使用finlize(),和C++有什麼不同,我竟然回答說可以在finalize()方法中處理一些關閉資源的操作(關閉文件等等)。汗顏!終結函數並不能保證會被及時地執行,從一個對象變的不可到達(通過對象網絡沒有了這個對象的引用),到它的終結函數被執行,這段時間的長短是任意的,不確定的。所以,時間關鍵的任務不應該由終結函數來完成,例如關閉一個已經被打開的文件。由於JVM延遲執行終結函數,所以大量的文件保留在打開狀態!而且終結函數的實現是不同的JVM中有不同的方法,所以你不能保證此函數的移植性。記住這點:
我們不應該依賴一個終結函數來更新關鍵性的永久狀態。
那麼我們該如何編程序來執行清理工作,通常提供一個顯式的終止方法,通常與tr..finally結構結合使用,這方面的例子最好的是Java.io裡面的各種流操作了,基本都有一個close()方法,你必須顯式地關閉打開的資源。終結函數的使用有兩個合理的方面:
1。充當最後一道“安全網”,在客戶端忘記或者不能調用顯式終止方法的時候。
2。調用本地對象的時候,本地對象不擁有關鍵性資源的前提下,終止方法完成必要的工作以釋放資源。