Effective Java:創(chuàng)建和銷毀對象

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

(Consider static factory methods instead of constructors)

靜態(tài)工廠方法的優(yōu)勢:

  1. 靜態(tài)工廠方法與構(gòu)造器不同的第一大優(yōu)勢在于食拜,它們有名稱队腐。如果構(gòu)造器的參數(shù)本身沒有確切地描述正被返回的對象充易,那么具有適當(dāng)名稱的靜態(tài)工廠會更容易使用稿静。
  2. 靜態(tài)工廠方法與構(gòu)造器不同的第二大優(yōu)勢在于,不必在每次調(diào)用它們的時候都創(chuàng)建一個新對象滞诺。 筆者注:緩存淋叶,重復(fù)利用
  3. 靜態(tài)工廠方法與構(gòu)造器不同的第三大優(yōu)勢在于,它們可以返回原返回類型的任何子類型的對象。 筆者注: 更大的靈活性癣缅,構(gòu)成service provider framework的基礎(chǔ)
  4. 靜態(tài)工廠方法的第四大優(yōu)勢在于屡立,在創(chuàng)建參數(shù)化類型實例的時候焚刺,它們使代碼變得更加簡潔蔓姚。 筆者注:得益于類型推導(dǎo)

靜態(tài)工廠方法的缺點:

  1. 靜態(tài)工廠類如果不含public或者protected構(gòu)造器,就不能被子類化。
    它們與其他的靜態(tài)方法實際上沒有任何區(qū)別。 筆者注: api文檔沒像constructor那樣明確標(biāo)注
  2. 靜態(tài)工廠方法的慣用方法名:
    valueOf
    of
    getInstance
    newInstance
    getType
    newType

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

(Consider a builder when faced with many constructor parameters)

一個類的大部分字段為可選字段的場合,構(gòu)建類有三種模式:
分別是重疊構(gòu)造器瓶埋,Javabean模式和Builder模式挤悉。
重疊構(gòu)造器的示例代碼:

// Telescoping constructor pattern - does not scale well! - Pages 11-12

public class NutritionFacts {
    private final int servingSize;   // (mL)            required
    private final int servings;      // (per container) required
    private final int calories;      //                 optional
    private final int fat;           // (g)             optional
    private final int sodium;        // (mg)            optional
    private final int carbohydrate;  // (g)             optional

    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;
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola =
            new NutritionFacts(240, 8, 100, 0, 35, 27);
    }
}

一長串類型相同的參數(shù)會導(dǎo)致一些微妙的錯誤畏梆。如果客戶端不小心顛倒了其中兩個參數(shù)的順序,編譯器也不會出錯浴捆,但是程序在運行時會出現(xiàn)錯誤的行為。

Javabean模式:調(diào)用一個無參構(gòu)造器來創(chuàng)建對象忿族,然后調(diào)用setter方法來設(shè)置每個必要的參數(shù),以及每個相關(guān)的可選參數(shù)碉考。
缺點:構(gòu)造過程中無法保證一致性;阻止了把類做成不可變的可能报腔。
Javabean模式示例代碼:

// JavaBeans Pattern - allows inconsistency, mandates mutability - Pages 12-13

public class NutritionFacts {
    // Parameters initialized to default values (if any)
    private int servingSize  = -1;  // Required; no default value
    private int servings     = -1;  //     "     "     "      "
    private int calories     = 0;
    private int fat          = 0;
    private int sodium       = 0;
    private int carbohydrate = 0;

    public NutritionFacts() { }

    // Setters
    public void setServingSize(int val)  { servingSize = val; }
    public void setServings(int val)     { servings = val; }
    public void setCalories(int val)     { calories = val; }
    public void setFat(int val)          { fat = val; }
    public void setSodium(int val)       { sodium = val; }
    public void setCarbohydrate(int val) { carbohydrate = val; }


    public static void main(String[] args) {
        NutritionFacts cocaCola = new NutritionFacts();
        cocaCola.setServingSize(240);
        cocaCola.setServings(8);
        cocaCola.setCalories(100);
        cocaCola.setSodium(35);
        cocaCola.setCarbohydrate(27);
    }
}

如果類的構(gòu)造器或者靜態(tài)工廠中具有多個參數(shù)拄查,設(shè)計這種類時役拴, Builder模式就是種不錯的選擇,特別是當(dāng)大多數(shù)參數(shù)都是可選的時候矢腻。與使用傳統(tǒng)的重疊構(gòu)造器模式相比初嘹,使用Builder模式的客戶端代碼將更易于閱讀和編寫,構(gòu)建器也比JavaBeans更加安全闯冷。
Builder模式示例代碼:

// Builder Pattern - Pages 14-15

public class NutritionFacts {
    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 {
        // 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 val)
            { calories = val;      return this; }
        public Builder fat(int val)
            { fat = val;           return this; }
        public Builder carbohydrate(int val)
            { carbohydrate = val;  return this; }
        public Builder sodium(int val)
            { sodium = val;        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;
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).
            calories(100).sodium(35).carbohydrate(27).build();
    }
}

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

(Enforce the singleton property with a private constructor or an enum type)

在Java 1.5發(fā)行版本之前洒琢,實現(xiàn)Singleton有兩種方法谭网。
第一種方法中愉择,公有靜態(tài)成員是個final字段:

// Singleton with public final field - Page 17
public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() { }

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    // This code would normally appear outside the class!
    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}

第二種方法中,公有的成員是個靜態(tài)工廠方法:

// Singleton with static factory - Page 17

public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() { }
    public static Elvis getInstance() { return INSTANCE; }

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    // This code would normally appear outside the class!
    public static void main(String[] args) {
        Elvis elvis = Elvis.getInstance();
        elvis.leaveTheBuilding();
    }
}

從Java 1.5發(fā)行版本起旧乞,實現(xiàn)Singleton還有第三種方法。只需編寫一個包含單個元素的枚舉類型大磺,該方法已經(jīng)成為實現(xiàn)Singleton的最佳方法逞壁。

// Enum singleton - the preferred approach - page 18
public enum Elvis {
    INSTANCE;

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    // This code would normally appear outside the class!
    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}

注意:客戶端可以借助AccessibleObject.setAccessible方法流济,通過反射機制調(diào)用私有構(gòu)造器。

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

(Enforce noninstantiability with a private constructor)

企圖通過將類做成抽象類來強制該類不可被實例化腌闯,這是行不通的绳瘟!
我們只要讓這個類包含私有構(gòu)造器它就不能被實例化了:

// Noninstantiable utility class
public class UtilityClass {
    // Suppress default constructor for noninstantiability
    private UtilityClass() {
        throw new AssertionError();
    }
}

副作用,它使得一個類不能被子類化姿骏。

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

(Avoid creating unnecessary objects)

Person 類的isBabyBoomer方法每次被調(diào)用時都會產(chǎn)生一個Calendar對象糖声,一個Timezone對象和兩個Date對象,性能開銷很大工腋。

// Creates lots of unnecessary duplicate objects - page 20-21

import java.util.*;

public class Person {
    private final Date birthDate;

    public Person(Date birthDate) {
        // Defensive copy - see Item 39
        this.birthDate = new Date(birthDate.getTime());
    }

    // Other fields, methods omitted

    // DON'T DO THIS!
    public boolean isBabyBoomer() {
        // Unnecessary allocation of expensive object
        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;
    }
}

正確的做法應(yīng)該是:

// Doesn't creates unnecessary duplicate objects - page 21

import java.util.*;

class Person {
    private final Date birthDate;

    public Person(Date birthDate) {
        // Defensive copy - see Item 39
        this.birthDate = new Date(birthDate.getTime());
    }

    // Other fields, methods

    /**
     * The starting and ending dates of the baby boom.
     */
    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 isBabyBoomer() {
        return birthDate.compareTo(BOOM_START) >= 0 &&
               birthDate.compareTo(BOOM_END)   <  0;
    }
}

要優(yōu)先使用基本類型而不是裝箱基本類型姨丈,要當(dāng)心無意識的自動裝箱。
以下代碼中的Long sum改為long sum將顯著提升性能擅腰。

public class Sum {
    // Hideously slow program! Can you spot the object creation?
    public static void main(String[] args) {
        Long sum = 0L;
        for (long i = 0; i < Integer.MAX_VALUE; i++) {
            sum += i;
        }
        System.out.println(sum);
    }
}

第6條:消除過期的對象引用

(Item 6: Eliminate obsolete object references)

一般而言蟋恬,只要類是自己管理內(nèi)存,程序員就應(yīng)該警惕內(nèi)存泄漏問題趁冈。
內(nèi)存泄漏的另一個常見來源是緩存歼争。
內(nèi)存泄漏的第三個常見來源是監(jiān)聽器和其他回調(diào)拜马。
以下示例代碼的pop()方法中,應(yīng)考慮增加elements[size] = null;

// Can you spot the "memory leak"?

import java.util.*;

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

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

    /**
     * Ensure space for at least one more element, roughly
     * doubling the capacity each time the array needs to grow.
     */
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

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

(Item 7: Avoid finalizers)

終結(jié)方法(finalizer) 通常是不可預(yù)測的沐绒,也是很危險的.一般情況下是不必要的俩莽。
使用終結(jié)方法有一個非常嚴重的(Severe)性能損失。
如果類的對象中封裝的資源(例如文件或者線程)確實需要終止乔遮, 只需提供一個顯式的終止方法扮超,顯式的終止方法通常與try-finally結(jié)構(gòu)結(jié)合起來使用蹋肮,以確保及時終止。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坯辩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子漆魔,更是在濱河造成了極大的恐慌坷檩,老刑警劉巖改抡,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異裸删,居然都是意外死亡,警方通過查閱死者的電腦和手機阵赠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來清蚀,“玉大人,你說我怎么就攤上這事枷邪。” “怎么了东揣?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嘶卧。 經(jīng)常有香客問我,道長芥吟,這世上最難降的妖魔是什么专甩? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮钉稍,結(jié)果婚禮上涤躲,老公的妹妹穿的比我還像新娘。我一直安慰自己贡未,他們只是感情好种樱,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著羞秤,像睡著了一般缸托。 火紅的嫁衣襯著肌膚如雪左敌。 梳的紋絲不亂的頭發(fā)上瘾蛋,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機與錄音矫限,去河邊找鬼哺哼。 笑死,一個胖子當(dāng)著我的面吹牛叼风,可吹牛的內(nèi)容都是我干的取董。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼无宿,長吁一口氣:“原來是場噩夢啊……” “哼茵汰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起孽鸡,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蹂午,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后彬碱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體豆胸,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年巷疼,在試婚紗的時候發(fā)現(xiàn)自己被綠了晚胡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡嚼沿,死狀恐怖估盘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情骡尽,我是刑警寧澤遣妥,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站爆阶,受9級特大地震影響燥透,放射性物質(zhì)發(fā)生泄漏沙咏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一班套、第九天 我趴在偏房一處隱蔽的房頂上張望肢藐。 院中可真熱鬧,春花似錦吱韭、人聲如沸吆豹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蘸拔,卻和暖如春环葵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背邓萨。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工缔恳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留的烁,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓铃芦,卻偏偏與公主長得像襟雷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子咧虎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348

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

  • Effective Java筆記一 創(chuàng)建和銷毀對象 第1條 考慮用靜態(tài)工廠方法代替構(gòu)造器 第2條 遇到多個構(gòu)造器參...
    圣騎士wind閱讀 384評論 0 2
  • 本文主題是創(chuàng)建和銷毀對象征唬,關(guān)注一下幾個問題: 何時以及如何創(chuàng)建對象 何時以及如何避免創(chuàng)建對象 如何去報它們能夠適時...
    666真666閱讀 323評論 2 1
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法总寒,類相關(guān)的語法理肺,內(nèi)部類的語法,繼承相關(guān)的語法妹萨,異常的語法,線程的語...
    子非魚_t_閱讀 31,597評論 18 399
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 3,805評論 0 11
  • 我發(fā)現(xiàn)自己一點都在受難節(jié)沒有悲痛的感覺,我的上帝為我受了如此多的苦難囱怕,我不該紀(jì)念嗎?昨天晚上公司聚餐,吃了喝了...
    天城美景閱讀 451評論 2 1