EffectiveJava第2章-創(chuàng)建和銷毀對(duì)象

第1條:考慮使用靜態(tài)工廠方法代替構(gòu)造器

獲取類實(shí)例的兩種方法:公有的構(gòu)造器、公有的靜態(tài)工廠方法返回類的實(shí)例。

靜態(tài)工廠方法的優(yōu)勢(shì)
1.它們有名稱
當(dāng)一個(gè)類需要多個(gè)帶有相同簽名的構(gòu)造器時(shí),就用靜態(tài)工廠方法代替構(gòu)造器,并且慎重的選擇名稱以便突出它們之間的區(qū)別。

2.不必每次調(diào)用它們的時(shí)候都創(chuàng)建一個(gè)新對(duì)象
可以避免創(chuàng)建不必要的重復(fù)對(duì)象。這種方法類似于享元模式,如果程序經(jīng)常請(qǐng)求創(chuàng)建相同的對(duì)象,可以使用靜態(tài)工廠方法。
創(chuàng)建單例的時(shí)候,也用到靜態(tài)工廠方法。

3.它們可以返回原返回類型的任何子類型的對(duì)象
這比較適用于基于接口的框架。API可以返回各種子對(duì)象,同時(shí)又不會(huì)使對(duì)象的類變成公有的。這樣的API變得非常整潔。比如java.util.Collections??蛻舳擞肋h(yuǎn)不知道也不關(guān)心他們從工廠方法中得到的對(duì)象的類,而且在后續(xù)的版本中發(fā)生變化也不會(huì)對(duì)客戶端造成影響。

4.在創(chuàng)建參數(shù)化類型實(shí)例的時(shí)候,它們使代碼變得更加簡潔

//調(diào)用參數(shù)化類的構(gòu)造器時(shí),必須要提供類型參數(shù)
Map<String, List<String>> m = new HashMap<String, List<String>>();

//工具類,提供靜態(tài)工廠方法創(chuàng)建參數(shù)化類型實(shí)例
public static <K,V> HashMap<K,V> newInstance(){
      return new HashMap<K,V>();
}

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

靜態(tài)工廠方法的缺點(diǎn)
1.類如果不含公有的或者受保護(hù)的構(gòu)造器,就不能被子類化。
不能被子類化就是不能被繼承。

2.靜態(tài)工廠方法與其他的靜態(tài)方法實(shí)際上沒有任何區(qū)別。

第2條:遇到多個(gè)構(gòu)造器參數(shù)時(shí)要考慮用構(gòu)造器

遇到參數(shù)比較多時(shí),一般的構(gòu)造器(重疊構(gòu)造器),首先提供一個(gè)只有必要參數(shù)的構(gòu)造器,第二個(gè)構(gòu)造器有一個(gè)可選參數(shù),第三個(gè)有兩個(gè)可選參數(shù),以此類推……重疊構(gòu)造器模式可行,但是當(dāng)有許多參數(shù)的時(shí)候,客戶端代碼會(huì)很難寫,并且較難閱讀。

//重疊構(gòu)造器的代碼示例
public class NutritionFacts {
   private final int servingSize;  //(ml)
   private final int servings;     //(per container)
   private final int calories;     //
   private final int fat;          //(g)
   private final int sodium;       //(mg)
   private final int carbohydrate; //(g)

   public NutritionFacts (int servingSize,int servings) {
       this(servingSize,servings,0);
   }
   public NutritionFacts (int servingSize,int servings,int calories) {
       this(servingSize,servings,calories,0);
   }

   public NutritionFacts (int servingSize,int servings,int calories,int fat) {
       this(servingSize,servings,calories,fat,0);
   }
   public NutritionFacts (int servingSize,int servings,int calories,int fat,int sodium) {
       this(servingSize,servings,calories,fat,sodium,0);
   }
   public NutritionFacts (int servingSize,int servings,int calories,int fat,int sodium,int carbohydrate) {
       this.servingSize=servingSize;
       this.servings=servings;
       this.calories=calories;
       this.fat=fat;
       this.sodium=sodium;
       this.carbohydrate=carbohydrate;
   }
}

第二種方式,即JavaBeans模式。只提供一個(gè)無參的構(gòu)造函數(shù)。然后調(diào)用setter方法來設(shè)置參數(shù)。在構(gòu)造過程中,JavaBean可能處于不一致的狀態(tài),類無法通過檢驗(yàn)參數(shù)的有效性來保證一致性。

public class NutritionFacts {
    private  int servingSize=-1;  //(ml)
    private  int servings=-1;     //(per container)
    private  int calories=0;     //
    private  int fat=0;          //(g)
    private  int sodium=0;       //(mg)
    private  int carbohydrate=0; //(g)

    public NutritionFacts () {}

    public void setServingSize(int servingSize) {this.servingSize = servingSize;}

    public void setServings(int servings) {this.servings = servings;}

    public void setCalories(int calories) {this.calories = calories;}

    public void setFat(int fat) {this.fat = fat;}

    public void setSodium(int sodium) {this.sodium = sodium;}

    public void setCarbohydrate(int carbohydrate) {this.carbohydrate = carbohydrate;}
}

第三種方式就是Builder模式。builder像個(gè)構(gòu)造器一樣,可以對(duì)其參數(shù)強(qiáng)加約束條件。build方法可以檢驗(yàn)這些約束條件。其實(shí)Builder就是一個(gè)JavaBean,只不過增加了一個(gè)build方法來檢查參數(shù)的有效性。

public class NutritionFacts {
    private  int servingSize=-1;  //(ml)
    private  int servings=-1;     //(per container)
    private  int calories=0;     //
    private  int fat=0;          //(g)
    private  int sodium=0;       //(mg)
    private  int carbohydrate=0; //(g)

    public static  class Builder{
        //Required parameters
        private final int servingSize;
        private final int servings;
        //Optional parameters - initialized to default values
        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 calories) {
            this.calories = calories;
            return this;
        }

        public Builder fat(int fat) {
            this.fat = fat;
            return this;
        }

        public Builder carbohydrate(int carbohydrate) {
            this.carbohydrate = carbohydrate;
            return this;
        }

        public Builder sodium(int sodium) {
            this.sodium = sodium;
            return this;
        }

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

    private NutritionFacts(Builder builder){
        servingSize=builder.servingSize;
        servings=builder.servings;
        calories=builder.calories;
        fat=builder.fat;
        sodium=builder.sodium;
        carbohydrate=builder.carbohydrate;
    }

}

第3條:用私有構(gòu)造器或者枚舉類型強(qiáng)化Singleton屬性

1.餓漢式(類加載時(shí)創(chuàng)建實(shí)例)

//final公有的靜態(tài)成員
public class Elvis01 {  
    public static final Elvis01 INSTANCE = new Elvis01();  
    private Elvis01(){  //私用構(gòu)造器僅被調(diào)用一次
        if(INSTANCE != NULL){
            throw new SomeException();
        }
        ......
    }  
    public void leaveTheBuilding(){  
        System.out.println("Elvis01 leaveTheBuilding");  
    }  
} 

客戶端通過反射機(jī)制調(diào)用私有構(gòu)造器,將構(gòu)造器修改成可訪問的,從而創(chuàng)建出另一個(gè)實(shí)例。所以可以在創(chuàng)建第二個(gè)實(shí)例的時(shí)候拋出異常。

2.餓漢變種(提供靜態(tài)方法獲取實(shí)例)

//公有的成員是個(gè)靜態(tài)工廠
public class Elvis02 implements Serializable{  
    private static final Elvis02 INSTANCE = new Elvis02();  
    private Elvis02(){  
  
    }  
    public void leaveTheBuilding(){  
        System.out.println("Elvis02 leaveTheBuilding");  
    }  
    public static Elvis02 getInstance(){  
        return INSTANCE;  
    }  
}

3.懶漢式

public class Elvis04 implements Serializable{  
    private Elvis04 instance;  
  
    private Elvis04(){  
  
    }  
    public Elvis04 getInstance(){  
        if(instance == null){  
            instance = new Elvis04();  
        }  
        return instance;  
    }  
} 

這是線程不安全的。

上述三種方式,在序列化的時(shí)候都一些額外的工作,否則沒有辦法保證單例,因?yàn)槊看畏葱蛄幸粋€(gè)序列化的實(shí)例的時(shí)候,都會(huì)創(chuàng)建一個(gè)新的實(shí)例。
必須聲明所有的實(shí)例域都是瞬時(shí)的(transient),并提供一個(gè)readResolve方法。

private Object readResolve(){
    return INSTANCE;
}

4.枚舉單例

public enum  Elvis03 implements Serializable{  
    INSTANCE;  
    public void leaveTheBuilding(){  
        System.out.println("Elvis03 leaveTheBuilding");  
    }  
}  

5.靜態(tài)內(nèi)部類
實(shí)現(xiàn)了懶加載,并且是線程安全的。
利用類加載機(jī)制,實(shí)現(xiàn)了線程安全:客戶端代碼調(diào)用了getInstance時(shí),JVM加載SingletonHolder,初始化靜態(tài)成員,從而實(shí)例化了instance。

public class Elvis05 implements Serializable{  
    private Elvis05(){  
  
    }  
    public Elvis05 getInstance(){  
        return SingletonHolder.instance;  
    }  
    private static class SingletonHolder{  
        private static Elvis05 instance = new Elvis05();  
    }  
    public void leaveTheBuilding(){  
        System.out.println("Elvis05 leaveTheBuilding");  
    }  
} 

第4條:通過私有構(gòu)造器強(qiáng)化不可實(shí)例化的能力

有些類不希望被實(shí)例化,比如說一些工具類:java.util.Collections。
在缺少顯式構(gòu)造器的情況下,編譯器會(huì)自動(dòng)提供一個(gè)缺省的公有的無參構(gòu)造器。這樣的類仍然可能被實(shí)例化。
要寫類不能被實(shí)例化,只要讓這個(gè)類只提供一個(gè)私有構(gòu)造器(private),就不能在外部被實(shí)例化。
這種做法使得這個(gè)類不能被子類化,因?yàn)樗械臉?gòu)造器都會(huì)顯式或則隱式地調(diào)用超類構(gòu)造器,在這種情況下,子類就沒有可訪問的超類構(gòu)造器可調(diào)用了。

第5條:避免創(chuàng)建不必要的對(duì)象

最好能重用對(duì)象,而不是每次需要的時(shí)候就創(chuàng)建一個(gè)相同功能的新對(duì)象。
如果對(duì)象是不可變的,它始終可以被重用。

String s = new String("stringette")//每次都會(huì)創(chuàng)建一個(gè)新的實(shí)例
String s = “stringette” //重用同一個(gè)對(duì)象

除了重用不可變的對(duì)象之外,也可以重用那些一直不會(huì)被修改的可變對(duì)象。

//書上的一個(gè)坑
public static void main(String[] args){
   Long sum = 0L;
   for(long i = 0 ; i < Integer.MAX_VALUE; i++){
       sum += i;
   }
   System.out.println(sum);
}

變量sum被聲明為Long,每次執(zhí)行sum += i的時(shí)候都要自動(dòng)裝箱,創(chuàng)建了很多不必要的Long對(duì)象。要優(yōu)先使用基本類型而不是裝箱基本類型,當(dāng)心無意識(shí)的自動(dòng)裝箱。

第6條:消除過期的對(duì)象引用

Java雖然有垃圾回收機(jī)制,但是并不意味著你不用手動(dòng)去管理內(nèi)存。
只要類是自己管理內(nèi)存,就應(yīng)該警惕內(nèi)存泄漏的問題。
下面就是內(nèi)存泄漏常見的三種情形:

//簡單的棧實(shí)現(xiàn),從棧中取出一個(gè)元素
public Object pop(){
    if(size == 0){
           throw new EmptyStackException();
     }
     return elements[--size];
}

這段程序存在著“內(nèi)存泄漏”。如果一個(gè)棧先是增長的,然后再收縮。那么,從棧中彈出來的對(duì)象將不會(huì)被當(dāng)做垃圾回收。因?yàn)闂?nèi)部維護(hù)著對(duì)這些對(duì)象的“過期引用”。

public Object pop(){
   if(size == 0){
           throw new EmptyStackException();
     }
     Object result = elements[--size];
     elements[size] = null;
     return result;
}

內(nèi)存泄漏的另一個(gè)常見的來源是緩存。一旦你把對(duì)象引用放到緩存中,他就很容易被遺忘掉,從而使它不再有用很長時(shí)間內(nèi)仍然留在緩存中。
可以使用WeakHashMap,自動(dòng)清除沒有被外部引用的鍵值。(緩存本身就是為了提高數(shù)據(jù)的讀取速度,如果在緩存中沒有讀到數(shù)據(jù),再去內(nèi)存中找。這并不會(huì)影響程序的正確性,所以WeakHashMap可以加快數(shù)據(jù)讀取速度,也能夠避免出現(xiàn)內(nèi)存泄漏,因?yàn)楸蝗跻藐P(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生之前。)
LinkedHashMap構(gòu)建LRU緩存。

第三個(gè)常見的來源:監(jiān)聽器和其他回調(diào)。客戶端通過API注冊(cè)了回調(diào),卻沒有顯式地取消注冊(cè),導(dǎo)致對(duì)象積聚。
最佳方法是只保存回調(diào)的弱引用。

第7條:避免使用終結(jié)方法

終結(jié)方法只會(huì)被調(diào)用一次。

大致描述一下finalize流程:當(dāng)對(duì)象變成(GC Roots)不可達(dá)時(shí),GC會(huì)判斷該對(duì)象是否覆蓋了finalize方法,若未覆蓋或者finalize方法已經(jīng)被執(zhí)行過,則直接將其回收。否則,若對(duì)象未執(zhí)行過finalize方法,將其放入F-Queue隊(duì)列,由一低優(yōu)先級(jí)線程執(zhí)行該隊(duì)列中對(duì)象的finalize方法。執(zhí)行finalize方法完畢后,GC會(huì)再次判斷該對(duì)象是否可達(dá),若不可達(dá),則進(jìn)行回收,否則,對(duì)象“復(fù)活”。(可對(duì)著下面的代碼看)

上述流程存在一個(gè)嚴(yán)重問題就是將fianlize方法交給一個(gè)低優(yōu)先級(jí)線程執(zhí)行,因此不能保證被及時(shí)地執(zhí)行,甚至根本不會(huì)保證被執(zhí)行。

替代方法就是提供一個(gè)顯式的終止方法。比如InputStream、OutputStream的close方法以及Timer的cancel方法。
顯式的終止方法通常與try-finally結(jié)構(gòu)結(jié)合起來使用,以確保及時(shí)終止。即使有異常拋出,finally塊也始終會(huì)執(zhí)行,因此終止方法肯定會(huì)被執(zhí)行。

對(duì)象再生問題:finalize方法中,可將待回收對(duì)象賦值給GC Roots可達(dá)的對(duì)象引用,從而達(dá)到對(duì)象再生的目的。

public class GC {  
 
   public static GC SAVE_HOOK = null;  
 
   public static void main(String[] args) throws InterruptedException {  
       SAVE_HOOK = new GC();  
       SAVE_HOOK = null;  
       System.gc();  
       Thread.sleep(500);  
       if (null != SAVE_HOOK) { //此時(shí)對(duì)象應(yīng)該處于(reachable, finalized)狀態(tài)  
           System.out.println("Yes , I am still alive");  
       } else {  
           System.out.println("No , I am dead");  
       }  
       SAVE_HOOK = null;  
       System.gc();  
       Thread.sleep(500);  
       if (null != SAVE_HOOK) {  
           System.out.println("Yes , I am still alive");  
       } else {  
           System.out.println("No , I am dead");  
       }  
   }  
 
   @Override  
   protected void finalize() throws Throwable {  
       super.finalize();  
       System.out.println("execute method finalize()");  
       SAVE_HOOK = this;  
   }  
}  

代碼執(zhí)行結(jié)果
execute method finalize()
Yes , I am still alive
No , I am dead

終結(jié)方法有兩種合理用途:
1.終結(jié)方法可以充當(dāng)安全網(wǎng),如果對(duì)象的所有者忘記調(diào)用了顯式的終止方法,那么終結(jié)方法可以充當(dāng)安全網(wǎng),遲一點(diǎn)釋放資源總比不釋放資源要好。同時(shí)輸出日志提示用戶去調(diào)用顯式的終止方法。

2.終止非關(guān)鍵的本地資源。如果是關(guān)鍵資源,必須調(diào)用終止方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評(píng)論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評(píng)論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評(píng)論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,779評(píng)論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評(píng)論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評(píng)論 0 291
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評(píng)論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,515評(píng)論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,750評(píng)論 1 375
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,933評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評(píng)論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,492評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,703評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,738評(píng)論 18 399
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 11,080評(píng)論 6 13
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,836評(píng)論 18 139
  • 1、.java源文件: 一個(gè)以”.java“為后綴的源文件:只能有一個(gè)與文件名相同的類,可以包含其他類。 2、類方...
    Hughman閱讀 1,502評(píng)論 1 9
  • 月上高懸,青燈初上,揮墨成影淚沾襟!這世界唯一的你,在哪里?
    可我不愛聰明閱讀 137評(píng)論 0 0