《Effective Java 中文版 第二版》第二章 第5條:避免創(chuàng)建不必要的對象

本章的主題是創(chuàng)建和銷毀對象:何時以及如何創(chuàng)建對象婉烟,何時以及如何避免創(chuàng)建對象,如何確保它們能夠適時地銷毀,以及如何管理對象銷毀之前必須進行的各種清理動作育瓜。


[toc]

一般來所,最好能重用對象而不是每次需要的時候就創(chuàng)建一個相同功能的新對象栽烂。重用方式既快速躏仇,又流行。如果對象是不可變的(immutable)(見第 15 條)愕鼓,它就始終可以被重用钙态。

String

書中舉了一個極端的反面例子:

String s = new String("stringette"); // DON'T DO THIS!

該語句每次執(zhí)行的時候都創(chuàng)建一個新的String實例,但是這是多余的,("stringette")本身就是一個 String 實例锰霜,如果此語句在循環(huán)中西雀,或是頻繁調(diào)用的方法中,將會創(chuàng)建出成千上萬不必要的 String 實例驻子。

改進后的版本如下所示:

String s = "stringette";

這個版本只用了一個 String 實例,而不是每次執(zhí)行的時候都創(chuàng)建一個新的實例估灿,而且它可以保證崇呵,對于所有在同一臺虛擬機中運行的代碼,只要它們包含相同的字符串字面常量馅袁,該對象就會被重用域慷。【這點跟 JVM 有關(guān),上一個版本每次都會 new 一個實例犹褒,此版本將先查找字符串池抵窒,在判斷是否創(chuàng)建對象】

原理:

  • 在JAVA虛擬機(JVM)中存在著一個字符串池,其中保存著很多String對象叠骑,并且可以被共享使用李皇,因此它提高了效率。由于String類是final的宙枷,它的值一經(jīng)創(chuàng)建就不可改變掉房,因此我們不用擔心String對象共享而帶來程序的混亂。字符串池由String類維護慰丛,我們可以調(diào)用intern()方法來訪問字符串池卓囚。
  • 而改進版本的這行代碼被執(zhí)行的時候,JAVA虛擬機首先在字符串池中查找是否已經(jīng)存在了值為"abc"的這么一個對象璧帝,它的判斷依據(jù)是String類equals(Object obj)方法的返回值捍岳。如果有,則不再創(chuàng)建新的對象睬隶,直接返回已存在對象的引用锣夹;如果沒有,則先創(chuàng)建這個對象苏潜,然后把它加入到字符串池中银萍,再將它的引用返回。

靜態(tài)工廠

對于同時提供了靜態(tài)工廠方法(見第1條)和構(gòu)造器的不可變類恤左,通程剑可以使用靜態(tài)工廠方法而不是構(gòu)造器。例如飞袋,Boolean.valueOf(String)Boolean(String)戳气,因為構(gòu)造器每次被調(diào)用的時候都會創(chuàng)建一個新的對象,而靜態(tài)工廠方法則從來不要求巧鸭,也不會這樣做瓶您。【構(gòu)造器需要 new 操作纲仍,靜態(tài)方法在 JVM 中早已分配固定的空間】

已知不會被修改的對象

除了重用不可變的對象之外呀袱,也可以重用那些已知不會被修改的可變對象。下面是一個比較微妙郑叠、也比較常見的反面例子夜赵。

其中設(shè)計可變的 Date對象,它們的值一旦計算出來之后就不再變化乡革。這個類建立了一個模型:其中有一個人寇僧,并有一個isBabyBoomer方法摊腋,用來檢驗這個人是否為一個"baby boomer(生育高峰期出身的小孩)",換句話所就是檢驗這個人是否出生于1946年至1964年期間婉宰。

public class Person {
    private final Date birthDate;
    // Other fields, methods, and constructor 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;
    }
}

isBabyBoomer每次被調(diào)用的時候歌豺,都會新建一個Calendar、一個TimeZone和兩個Date實例心包,這是不必要的開銷。

靜態(tài)的初始化器

此版本用一個靜態(tài)的初始化器(initializer)馒铃,避免了這種效率低下的情況蟹腾。

class Person {
    private final Date birthDate;
    // Other fields, methods, and constructor omitted
    
    /**
    * 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;
    }
}
  • 改進后的 Person 類只在初始化的時候創(chuàng)建 Calendar、TimeZone 和 Date 實例一次区宇,而不是在每次調(diào)用 isBabyBoomer 的時候創(chuàng)建這些實例娃殖。
  • 在作者的機器上,沒調(diào)用一千萬此议谷,原版本需要 32 000ms 炉爆,而改進版本只需要 130 ms ,大約快了250倍卧晓。性能顯著提高芬首,代碼含義也更加清晰。
  • 可以通過延遲初始化(lazily initializing(見第71條))逼裆,將BOOM_START BOOM_END初始化延遲到第一次調(diào)用isBabyBoomer的時候郁稍,但不建議,這樣會是方法的實現(xiàn)更加浮渣胜宇,無法將性能顯著提高到超過已經(jīng)達到的水平(見第55條)耀怜。

自動裝箱(autoboxing)

在Java1.5發(fā)行版中,允許程序員將基本類型和他們的裝箱基本類型(包裝類(Boxed Primitive Type))混用桐愉,感覺很神奇财破,它使得基本類型和它們的包裝類之間變得模糊,其實只是語法糖从诲,底層依然老老實實的構(gòu)造裝箱基本類型實例【可通過反編譯查看】左痢。如果無意識的在循環(huán)中使用了此技術(shù),將創(chuàng)建非常多個中間對象盏求,使性能繼續(xù)下降【Long sum抖锥,long i】:

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

要優(yōu)先使用基本類型而不是裝箱基本類型,要當心無意識的自動裝箱碎罚。

總結(jié)

不要錯誤地認為本條目所介紹的內(nèi)容暗示著“創(chuàng)建對象的代價非常昂貴磅废,我們應(yīng)該要盡可能地避免創(chuàng)建對象”。相反荆烈,由于小對象的構(gòu)造器只做很少量的顯式工作拯勉,所以小對象的創(chuàng)建和回收動作是非常廉價的竟趾,特別是現(xiàn)代的JVM實現(xiàn)上更是如此。通過創(chuàng)建附加的對象宫峦,提升程序的清晰性岔帽、簡潔性和功能性,這通常是好事导绷。

  • 除非是非常重量級的對象犀勒,如數(shù)據(jù)庫鏈接池等,否則應(yīng)該避免維護自己的對象池(object pool)來避免創(chuàng)建對象⊥浊現(xiàn)代的JVM實現(xiàn)具有高度優(yōu)化的垃圾回收器(CG)贾费,其性能很容易就會超過輕量級對象池的性能。

本條目對象的是第39條中有關(guān)“保護性拷貝(defensive copying)”的內(nèi)容檐盟。本條目提及“當你應(yīng)該重用現(xiàn)有對象的時候褂萧,請不要創(chuàng)建新的對象”,而第39條則說“當你應(yīng)該創(chuàng)建新對象的時候葵萎,請不要重用現(xiàn)有對象”导犹。注意,在提倡使用保護性拷貝的時候羡忘,因重用對象而付出的代價要遠遠大于因創(chuàng)建重復(fù)對象而付出的代價谎痢。必要時如果沒能實施保護性拷貝,將會導(dǎo)致潛在的錯誤和安全漏洞壳坪;而不必要地創(chuàng)建對象則只會影響程序的風(fēng)格和性能舶得。

  • 類似于一個副本的概念:假設(shè)在圖書館借了一本書,如果你直接在上面做筆記爽蝴,但這本書是共享的沐批,將影響他人借閱。如果你是花錢復(fù)印一本蝎亚,在復(fù)印書上做筆記就不同了九孩。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市发框,隨后出現(xiàn)的幾起案子躺彬,更是在濱河造成了極大的恐慌,老刑警劉巖梅惯,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宪拥,死亡現(xiàn)場離奇詭異,居然都是意外死亡铣减,警方通過查閱死者的電腦和手機她君,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葫哗,“玉大人缔刹,你說我怎么就攤上這事球涛。” “怎么了校镐?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵亿扁,是天一觀的道長。 經(jīng)常有香客問我鸟廓,道長从祝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任肝箱,我火速辦了婚禮哄褒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘煌张。我一直安慰自己,他們只是感情好退客,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布骏融。 她就那樣靜靜地躺著,像睡著了一般萌狂。 火紅的嫁衣襯著肌膚如雪档玻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天茫藏,我揣著相機與錄音误趴,去河邊找鬼。 笑死务傲,一個胖子當著我的面吹牛凉当,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播售葡,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼看杭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了挟伙?” 一聲冷哼從身側(cè)響起楼雹,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎尖阔,沒想到半個月后贮缅,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡介却,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年谴供,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片筷笨。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡憔鬼,死狀恐怖龟劲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情轴或,我是刑警寧澤昌跌,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站照雁,受9級特大地震影響蚕愤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜饺蚊,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一萍诱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧污呼,春花似錦裕坊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至苗缩,卻和暖如春饵蒂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背酱讶。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工退盯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人泻肯。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓渊迁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親软免。 傳聞我的和親對象是個殘疾皇子宫纬,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

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

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法膏萧,內(nèi)部類的語法漓骚,繼承相關(guān)的語法,異常的語法榛泛,線程的語...
    子非魚_t_閱讀 31,664評論 18 399
  • 轉(zhuǎn)自:http://blog.csdn.net/jackfrued/article/details/4492194...
    王帥199207閱讀 8,538評論 3 93
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理蝌蹂,服務(wù)發(fā)現(xiàn),斷路器曹锨,智...
    卡卡羅2017閱讀 134,708評論 18 139
  • 你有過一個人斥废,一天不說話嗎? 今天早上出去買早飯给郊,有個四五歲的小弟弟對我喊:姐姐好~我也非常喜悅的回復(fù)牡肉,你也...
    Miss月色閱讀 327評論 0 0
  • 回去,還是不回淆九,這個問題一直困擾著我统锤,很久了。 留下炭庙,可以找無數(shù)個理由饲窿,離開,或許只需要一個理由焕蹄。但是這理由逾雄,須得...
    芣苡zx閱讀 154評論 0 2