第二章、創(chuàng)建和銷毀對象

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

靜態(tài)工廠方法與構(gòu)造器相比的優(yōu)勢:

  1. 有名稱;
  2. 不必再每次調(diào)用他們的時(shí)候都創(chuàng)建一個(gè)新對象;
  3. 可以返回原返回類型的任何子類型的對象;
  4. 在創(chuàng)建參數(shù)化類型實(shí)例時(shí),代碼更加簡潔。

靜態(tài)工廠方法的缺點(diǎn)

  1. 類如果不含公有的或者受保護(hù)的構(gòu)造器,就不能被子類化;
  2. 它們與其他的靜態(tài)方法實(shí)際上沒有任何區(qū)別,無法標(biāo)記(通過命名規(guī)則來標(biāo)記)。

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

當(dāng)構(gòu)造器有多個(gè)參數(shù)時(shí)常用的方法:

  1. 重疊構(gòu)造(telescoping constructor):缺點(diǎn)在于客戶端代碼難以編寫且難閱讀

  2. JavaBean模式,調(diào)用一個(gè)無參構(gòu)造器來創(chuàng)建對象,然后調(diào)用setter方法來設(shè)置必要的參數(shù)。
    缺點(diǎn)在于構(gòu)造過程被分到了多個(gè)調(diào)用中,可能存在不一致的狀態(tài),安全性能不能保證(需付出額外的努力);且對象不能是不可變的。

  3. 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的方法:

  1. 構(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。

  2. 第二種對應(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;
     
     }
    
  3. 第三種方法:編寫一個(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)建不必要的對象

  1. 最好能重用對象而不是在每次需要的時(shí)候就創(chuàng)建一個(gè)相同功能的新對象。重用方式既快速又流行,如果對象是不可變的(immutable),它就始終可以被重用。

  2. 對于同時(shí)提供靜態(tài)工廠方法和構(gòu)造器的不可變類,通常可以使用靜態(tài)工廠方法而不是構(gòu)造器,以避免創(chuàng)建不必要的對象。

  3. 也可以重用那些已知不會被修改的可變對象,可以用一個(gè)靜態(tài)的初始化器(initializer)避免調(diào)用方法就創(chuàng)建對象效率低下的情況。

  4. 適配器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í)例支撐的。

  5. 自動裝箱(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;
    }
}

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

  1. 內(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)存泄露的問題。

  2. 內(nèi)存泄露的另一個(gè)常見來源是緩存

    一旦把對象引用放入緩存中,很容易遺忘。
    解決方案:當(dāng)緩存過期后它們就會自動被清除。

  3. 內(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ù)的最合適工具。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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