Effective Java 3rd 條目6 避免創(chuàng)建不必要的對(duì)象

這通常是恰當(dāng)?shù)模褐赜脝蝹€(gè)對(duì)象昌阿,而不是每次需要的時(shí)候創(chuàng)建一個(gè)新的功能相同的對(duì)象山害。重用既更快又更有風(fēng)格村生。如果不可變,一個(gè)對(duì)象總是可以被重用(條目17)妻熊。

作為不應(yīng)該做的事情的一個(gè)極端例子夸浅,考慮這個(gè)語(yǔ)句:

String s = new String("bikini");// 不要這么做!

每次執(zhí)行時(shí)這個(gè)語(yǔ)句時(shí)創(chuàng)建一個(gè)新的String實(shí)例扔役,這些對(duì)象創(chuàng)建沒有一個(gè)是必要的帆喇。String構(gòu)造子的參數(shù)("bikini")自身是一個(gè)String實(shí)例,在功能上和由構(gòu)造子創(chuàng)建的所有對(duì)象是相同的亿胸。如果這個(gè)用法在一個(gè)循環(huán)里面或者在一個(gè)經(jīng)常被調(diào)用的方法里面坯钦,那么可能不必要地創(chuàng)建了上百萬(wàn)的String實(shí)例法严。
改進(jìn)版本僅僅如下:

String s = "bikini";

這個(gè)版本使用單個(gè)String實(shí)例,而不是每次執(zhí)行時(shí)創(chuàng)建一個(gè)新的對(duì)象葫笼。此外,這也保證了這個(gè)對(duì)象被運(yùn)行在同一個(gè)虛擬機(jī)中的其他代碼重用拗馒,這些代碼恰好有相同的字符串字面量[JLS, 3.10.5]路星。

通常,你可以優(yōu)先用靜態(tài)工廠方法(static factory method)(條目1)避免創(chuàng)建不必要的對(duì)象诱桂,而不是提供兩者的不可變類的構(gòu)造子洋丐。比如,Boolean.valueOf(String)方法比構(gòu)造子Boolean(String)更可取挥等,這個(gè)構(gòu)造子在Java 9中被棄用了友绝。構(gòu)造子必須 每次被調(diào)用時(shí)創(chuàng)建一個(gè)新的對(duì)象,而在實(shí)踐上工廠方法從來(lái)沒有要求這么做肝劲,也不應(yīng)該這么做迁客。除了重用不變對(duì)象,如果你知道可變對(duì)象不會(huì)被改變辞槐,你也可以重用它們掷漱。

一些對(duì)象創(chuàng)建比其他的更加昂貴。如果你反復(fù)地需要這樣的“昂貴對(duì)象”榄檬,為了重用而緩存是更加明智的卜范。不幸的是,當(dāng)你創(chuàng)建這樣的對(duì)象鹿榜,不總是明顯的海雪。假設(shè)你想編寫一個(gè)方法,決定一個(gè)字符串是否是一個(gè)有效的羅馬數(shù)字舱殿。這里是用正則表達(dá)式這種最容易的方式來(lái)做這件事:

// 性能可以大大的提高
static boolean isRomanNumeral(String s) { 
    return s.matches("^(?=.)M*(C[MD]|D?C{0,3})" 
        + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); 
}

這個(gè)實(shí)現(xiàn)的問題在于奥裸,它依賴于String.matches方法。雖然String.matches是最容易的方式檢測(cè)一個(gè)字符串是否符合正則表達(dá)式怀薛,在一個(gè)性能要求嚴(yán)格的情形中重復(fù)使用刺彩,是不適合的。這個(gè)問題是枝恋,它內(nèi)部為正則表達(dá)式創(chuàng)建了一個(gè)Pattern實(shí)例创倔,而且僅僅使用一次,在這之后焚碌,它符合垃圾回收的條件了畦攘。創(chuàng)建一個(gè)Pattern實(shí)例是相當(dāng)昂貴的,因?yàn)樗枰颜齽t表達(dá)式編譯成一個(gè)受限的狀態(tài)機(jī)十电。

為了提高性能知押,把正則表達(dá)式顯示地編譯成一個(gè)Pattern實(shí)例(是不可變的)叹螟,并作為類初始化的一部分,緩存它台盯,然后對(duì)于每個(gè)isRomanNumeral方法的調(diào)用罢绽,重用同一個(gè)實(shí)例:

// 為了性能,重用昂貴的對(duì)象
public class RomanNumerals {
    private static final Pattern ROMAN = Pattern.compile( 
        "^(?=.)M*(C[MD]|D?C{0,3})" 
        + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");

    static boolean isRomanNumeral(String s) { 
        return ROMAN.matcher(s).matches(); 
    }
}

isRomanNumeral的改進(jìn)版静盅,如果經(jīng)常調(diào)用良价,會(huì)有顯著的性能改進(jìn)。在我的機(jī)器中蒿叠,8個(gè)字符的輸入字符串對(duì)于原來(lái)的版本需要1.1微妙明垢,然而改進(jìn)版本花了0.17微妙,快了6.5倍市咽。不僅僅是性能改進(jìn)了痊银,而且可論證,清晰度也改進(jìn)了施绎。把原本不可見的Pattern實(shí)例變成一個(gè)靜態(tài)final域溯革,使得我們可以給它取一個(gè)名字,這樣就比正則表達(dá)式本身遠(yuǎn)遠(yuǎn)有更可讀粘姜。

如果一個(gè)人類初始化鬓照,這個(gè)類包含改進(jìn)版本的isRomanNumeral方法,但是這個(gè)方法從來(lái)沒有被調(diào)用孤紧,那么ROMAN域沒有必要初始化豺裆。當(dāng)isRomanNumeral方法第一次被調(diào)用時(shí),用懶初始化這個(gè)域(條目83)号显,可以消除這個(gè)初始化臭猜,這是有可能的,但是不建議這么做押蚤。就像通常懶初始化情形一樣蔑歌,這使得實(shí)現(xiàn)復(fù)雜而沒有可測(cè)量的性能提升(條目67)。

當(dāng)一個(gè)對(duì)象不可變揽碘,顯然可以被安全復(fù)用次屠,但是有其他的情形,是遠(yuǎn)遠(yuǎn)不顯而易見的雳刺,甚至反直覺的劫灶。考慮適配器(adapters)[Gamma95]情形掖桦,也叫視圖(views)本昏。一個(gè)適配器是一個(gè)對(duì)象,代理一個(gè)支撐對(duì)象枪汪,提供了一個(gè)可供選擇的接口涌穆。因?yàn)檫m配器沒有它支撐對(duì)象之外的狀態(tài)怔昨,所以對(duì)于給定對(duì)象的給定適配器,沒必要?jiǎng)?chuàng)建一個(gè)以上的實(shí)例宿稀。

比如趁舀,Map接口的keySet方法返回一個(gè)Map對(duì)象的Set視圖,包含了映射的所有鍵祝沸。乍一看赫编,好像每次調(diào)用keySet都會(huì)創(chuàng)建一個(gè)新的Set實(shí)例,但是在給定的Map對(duì)象上每次調(diào)用keySet奋隶,可能返回同一個(gè)Set實(shí)例。雖然返回的Set實(shí)例通常是可變的悦荒,但是所有返回對(duì)象在功能上相同的:當(dāng)返回對(duì)象之一改變了唯欣,所有其他的也會(huì)改變,這是因?yàn)樗鼈冇赏粋€(gè)Map實(shí)例支撐搬味。雖然創(chuàng)建keySet視圖對(duì)象的多個(gè)實(shí)例境氢,在大部分情況下是無(wú)害的,但是這是沒必要的而且無(wú)益的碰纬。

創(chuàng)建不必要的對(duì)象的另外一個(gè)方式是自動(dòng)裝箱萍聊,自動(dòng)裝箱讓程序員混合原始值和原始裝箱類型,有必要時(shí)會(huì)自動(dòng)裝箱和拆箱悦析。自動(dòng)裝箱模糊了但是沒有消除原始和原始裝箱類型的區(qū)別寿桨。這有微妙的語(yǔ)義的區(qū)別和不怎么微妙的性能差異(條目61)∏看鳎考慮如下方法亭螟,它計(jì)算所有正整數(shù)值之和。為了做這件事骑歹,程序用長(zhǎng)整形計(jì)算预烙,因?yàn)檎麛?shù)大小不夠容納所有正整數(shù)值之和:

// 相當(dāng)慢,你能指出對(duì)象創(chuàng)建嗎道媚?
private static long sum() {
    Long sum = 0L; 
    for (long i = 0; i <= Integer.MAX_VALUE; i++) 
        sum += i;

    return sum;
}

這個(gè)程序有正確的答案扁掸,但是比本應(yīng)該的要慢很多,這由于一個(gè)字符印刷上的錯(cuò)誤最域。sum變量被聲明為一個(gè)Long谴分,而不是long,這意味著程序構(gòu)造了231個(gè)沒必要的Long實(shí)例(長(zhǎng)整形i添加到Long sum時(shí)大約每次一個(gè))羡宙。sum的聲明從Long改變到long狸剃,在我的機(jī)器上運(yùn)行時(shí)間6.3秒減少到0.59秒。這個(gè)教訓(xùn)是很清楚的:原始類型優(yōu)于原始裝箱類型狗热,小心無(wú)意的自動(dòng)裝箱钞馁。

這條不應(yīng)該誤解為暗示:對(duì)象創(chuàng)建是很昂貴的虑省,應(yīng)該避免。相反僧凰,創(chuàng)建和回收小對(duì)象探颈,它的構(gòu)造子只做少量的顯的工作,是很廉價(jià)的训措,特別是在現(xiàn)代的JVM實(shí)現(xiàn)中伪节。創(chuàng)建額外的對(duì)象來(lái)增強(qiáng)程序的清晰性,簡(jiǎn)單性和能力绩鸣,通常是一件好事怀大。

相反,維護(hù)你自己的對(duì)象池來(lái)避免對(duì)象創(chuàng)建呀闻,是一個(gè)壞主意化借,除非池中的對(duì)象是相當(dāng)重量級(jí)的。證明對(duì)象池正當(dāng)?shù)牡湫屠邮菙?shù)據(jù)庫(kù)鏈接捡多。建立鏈接的代價(jià)是十分高的蓖康,以至于重用這些對(duì)象有意義。然而垒手,通常來(lái)講蒜焊,維護(hù)你自己的對(duì)象池使你的代碼凌亂,增加了內(nèi)存占用和性能損害】票幔現(xiàn)代JVM實(shí)現(xiàn)已經(jīng)高度優(yōu)化了垃圾回收泳梆,可以很容易比輕量級(jí)對(duì)象的對(duì)象池做的更好。

這個(gè)條目對(duì)應(yīng)的是關(guān)于防御性拷貝的條目50榜掌。當(dāng)前條目說(shuō)鸭丛,“當(dāng)你需要復(fù)用已經(jīng)存在的對(duì)象時(shí)不要?jiǎng)?chuàng)建一個(gè)新的對(duì)象”,然后條目50說(shuō)唐责,“當(dāng)你應(yīng)該創(chuàng)建一個(gè)新的對(duì)象時(shí)不要復(fù)用已經(jīng)存在的對(duì)象”鳞溉。注意,當(dāng)要求防御性拷貝時(shí)重用對(duì)象帶來(lái)的懲罰鼠哥,遠(yuǎn)遠(yuǎn)大于沒必要地創(chuàng)建重復(fù)對(duì)象的懲罰熟菲。有要求而不能夠生成防御性拷貝,會(huì)導(dǎo)致潛伏的缺陷和安全漏洞朴恳;創(chuàng)建不必要的對(duì)象僅僅影響風(fēng)格和性能抄罕。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市于颖,隨后出現(xiàn)的幾起案子呆贿,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件做入,死亡現(xiàn)場(chǎng)離奇詭異冒晰,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)竟块,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門壶运,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人浪秘,你說(shuō)我怎么就攤上這事蒋情。” “怎么了耸携?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵棵癣,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我夺衍,道長(zhǎng)浙巫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任刷后,我火速辦了婚禮,結(jié)果婚禮上渊抄,老公的妹妹穿的比我還像新娘尝胆。我一直安慰自己,他們只是感情好护桦,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布含衔。 她就那樣靜靜地躺著,像睡著了一般二庵。 火紅的嫁衣襯著肌膚如雪贪染。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天催享,我揣著相機(jī)與錄音杭隙,去河邊找鬼。 笑死因妙,一個(gè)胖子當(dāng)著我的面吹牛痰憎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播攀涵,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼铣耘,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了以故?” 一聲冷哼從身側(cè)響起蜗细,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎怒详,沒想到半個(gè)月后炉媒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體踪区,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年橱野,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了朽缴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡水援,死狀恐怖密强,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蜗元,我是刑警寧澤或渤,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站奕扣,受9級(jí)特大地震影響薪鹦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜惯豆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一池磁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧楷兽,春花似錦地熄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至揭厚,卻和暖如春却特,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背筛圆。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工裂明, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人太援。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓漾岳,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親粉寞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尼荆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

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