effective java中提供了57條建議。針對這些建議,我談談自己的理解。
1.考慮用靜態工廠方法代替構造函數
靜態工廠方式相比于構造函數的兩個優點:
1)可以有符合自己身份的方法名,方便客戶端代碼的閱讀
2)調用的時候,不要求創建一個新的實例。可以返回緩存實例,或者singleton實例等
靜態工廠方法的最大缺點:
如果類中沒有public或者protected的構造函數,使用靜態工廠方法的方式得到實例,那么這個類就無法被繼承。
比如
public class Demo {
private static Demo demo = new Demo();
public static Demo getInstance() {
return demo;
}
private Demo() {
}
}
那么這個類就無法被繼承。
(當然,鼓勵使用組合,而不是繼承)
在spring沒有流行起來的那些日子里,我大量使用工廠方法,但是使用spring等ioc容器后,這一切都是交給容器去處理了。或許,在客戶端代碼中,工廠模式會因為這些ioc的出>現,而遭受淘汰。
2.使用私有構造函數強化singleton屬性
一旦存在public或者protected的構造函數,那么無法保證一個類,一定是sinleton的。因為無法得知客戶端代碼是使用構造函數,還是同構靜態方法去得到類實例。所以對于一個嚴格要求singleton的類,那么其構造函數必須是私有的。
既然說到singleton了,那么順便說下幾個常見的創建方法
1)
/**
* 優點:簡單,而且可以確保一定是singletion實例
* 缺點:類加載時,就會初始化實例,不能做到延遲初始化。
*/
public class Demo {
private static final Demo demo = new Demo();
public static Demo getInstance() {
return demo;
}
private Demo() {
}
}
2)
/**
* 優點:lazy load(延遲初始化實例),提高效率
* 缺點:多線程情況下,可能初始化多份實例
*/
public class Demo {
private static Demo demo = null;
public static Demo getInstance() {
if(demo == null ) {
demo = new Demo();
}
return demo;
}
private Demo() {
}
}
3)
/**
* 優點:lazy load(延遲初始化實例),提高效率
* 采用double check并且同步的方式,理論上確保在多線程的應用場景中,也只創建一份實例
* 備注:(涉及到jvm的實現,在實際應用中,也可能生成多份實例,但是幾率是相當地低)
*/
public class Demo {
private static Demo demo = null;
public static Demo getInstance() {
if(demo == null ) {
synchronized(Demo.class) {
if(demo == null) {
demo = new Demo();
}
}
}
return demo;
}
private Demo() {
}
}
3.使用私有構造函數強化不可實例化能力
咋一看這個標題,覺得不可思議,居然讓類不具備實例化能力。但是確實也有一些應用場景,比如一些util類,就不需要實例化。但是有很大的副作用,就是類無法被繼承。所以換成我,就算是util類,我還是會保留其public的構造函數的。客戶端就算要實例化這些util,也無傷大雅。
4.避免創建重復對象
一般情況下,請重復使用同一個對象,而不是每次需要的時候創建一個功能上等價的新對象。這主要是為了性能上的考慮,何況在一般的應用場景下,確實沒有必要去重復創建對象。當然有時候為了OO設計考慮,也不特別排斥創建重復的小對象。
需要明確的是,避免創建重復的對象,請不要產生一個誤區就是:創建對象的成本非常昂貴。事實上,創建小對象的開銷是非常小的,而且現在的jdk gc對于小對象的GC代價也是非常廉價(在之后的日子里,我會針對sun jdk gc,做一次介紹)。比如在做Swing開發的時候,會創建很多EventListener對象,比如在Spring Framework中,就創建很多匿名內隱類對象實現類似ruby等動態語言的Closure(閉包)。
但是也不可否認的是,創建大對象,對大對象的GC 的開銷是比較大的。比如初始化一個對象的時候,需要加載10m的文件內容到內存;創建數據庫連接對象等等,在這些場景下,創建的開銷是相當昂貴了,一定要盡可能避免重復對象的創建(除非特殊需求)。
對于這些大對象,一般采用singleton模式,cache,或者object pool等方式,避免重復的創建。至于采用具體什么方式,需要根據具體的應用場景來決定了。
5.消除過期對象的引用
為什么要這么做?其實只要理解“java內存泄露”這個概念就可以了。java中的內存泄漏不同于C++中的內存泄漏。C++是需要程序員手工管理內存的語言,創建一個對象,用完之后,需要手工刪除這個對象。而java不一樣,jdk gc替程序員做了這件事情,一旦對象失去了引用之后,jdk gc就會自動回收這個對象。
為了避免java中的內存泄漏,只需要知道一點就可以:對于無用的對象,必須消除對這個對象的引用。
怎么消除對象的引用呢?難道需要手工設置“object=null;”,那么一旦程序中到處充斥著這樣的代碼,將會是一件非常惡心的事情,嚴重影響程序的閱讀性。
正確的做法是,每個定義的變量給予最緊湊的作用域,一旦對象結束生命周期,自然就消除了對其的引用。
當然在必要的場合,也不反對采用清空對象引用的方法,但是不推薦。
內存泄漏的癥狀不能在短時間內反應出來,往往是在程序運行一段時間,一天,一周,一個月,甚至一年,才逐漸顯現的,一個經驗豐富的程序員,也不能確保其代碼一定不存在內存泄漏的問題。檢查內存泄漏問題,往往需要借助工具,heap profile,比如jprofile等等。在linux下面,可以使用jps jstat jinfo jmap等命令去觀察。