一般來說峻黍,最好是重用對象而不是在每次需要的時(shí)候就創(chuàng)建一個(gè)相同功能的新對象躁劣。重用方式既更加快速促煮,也更為流行邮屁。如果對象是不可變的(immutable)(見第15條),它就始終可以被重用菠齿。
作為一個(gè)極端的反面例子佑吝,考慮下面的語句:
String s = new String("hello world"); // DON'T DO THIS!
該語句每次被執(zhí)行的時(shí)候都創(chuàng)建一個(gè)新的String實(shí)例,但是這些創(chuàng)建對象的動(dòng)作全都是不必要的绳匀。傳遞給String構(gòu)造器的參數(shù)("hello world")本身就是一個(gè)String實(shí)例芋忿,功能方面等同于構(gòu)造器創(chuàng)建的所有對象。如果這種用法是在一個(gè)循環(huán)中疾棵,或者是在一個(gè)被頻繁調(diào)用的方法中戈钢,就會(huì)創(chuàng)建出成千上萬不必要的String實(shí)例。
改進(jìn)后的版本如下所示:
String s = "hello world";
這個(gè)版本只用了一個(gè)String實(shí)例是尔,而不是每次執(zhí)行的時(shí)候都創(chuàng)建一個(gè)新的實(shí)例殉了。而且,它可以保證拟枚,對于所有在同一臺(tái)虛擬機(jī)中運(yùn)行的代碼薪铜,只要它們包含相同的字符串字面常量众弓,該對象就會(huì)被重用[JLS, 3.10.5]。
對于同時(shí)提供了靜態(tài)工廠方法(見第1條)和構(gòu)造器的不可變類隔箍,通澄酵蓿可以使用靜態(tài)工廠方法而不是構(gòu)造器,以避免創(chuàng)建不必要的對象蜒滩。例如滨达,靜態(tài)工廠方法Boolean.valueOf (String)幾乎總是優(yōu)先于構(gòu)造器Boolean(String)。構(gòu)造器在每次被調(diào)用的時(shí)候都會(huì)創(chuàng)建一個(gè)新的對象俯艰,而靜態(tài)工廠方法則從來不要求這樣做弦悉,實(shí)際上也不會(huì)這樣做。
除了重用不可變的對象之外蟆炊,也可以重用那些已知不會(huì)被修改的可變對象稽莉。下面是一個(gè)比較微妙、也比較常見的反例涩搓,其中涉及到可變的Date對象污秆,它們的值一旦計(jì)算出來之后就不會(huì)再有變化。這個(gè)類建立了一個(gè)模型:其中有一個(gè)人昧甘,并有一個(gè)isBabyBoomer方法良拼,用來檢驗(yàn)這個(gè)人是否為一個(gè)"baby boomer(生育高峰期出生的小孩)",換句話說充边,就是檢驗(yàn)這個(gè)人是否出生于1946至1964年間:
isBabyBoomer每次被調(diào)用的時(shí)候庸推,都會(huì)新建一個(gè)Calendar、一個(gè)TimeZone和兩個(gè)Date實(shí)例浇冰,這是沒有必要的贬媒。下面的版本使用了一個(gè)靜態(tài)的初始化器(initializer),避免了這種效率低下的情況:
改進(jìn)后的Person類只在初始化的時(shí)候創(chuàng)建Calendar肘习、TimeZone和Date實(shí)例一次际乘,而不是在每次isBabyBoomer被調(diào)用的時(shí)候都創(chuàng)建這些實(shí)例。如果isBabyBoomer方法得到頻繁的調(diào)用漂佩,這種方法將會(huì)顯著地提高性能脖含。在我的機(jī)器上,每調(diào)用一千萬次投蝉,原來的版本需要32,000ms养葵,而改進(jìn)后的版本只需130ms,大約快了250倍瘩缆。除了提高性能之外关拒,代碼的含義也更加清晰了。把boomStart和boomEnd從局部變量改為final靜態(tài)域,這些日期顯然是被作為常量對待夏醉,從而使得代碼更易于理解爽锥。但是,這種優(yōu)化帶來的效果并不總是那么明顯畔柔,因?yàn)镃alendar實(shí)例的創(chuàng)建代價(jià)特別昂貴氯夷。
如果改進(jìn)后的Person類被初始化了,它的isBabyBoomer方法卻永遠(yuǎn)不會(huì)被調(diào)用靶擦,那就沒有必要初始化BOOM_START和BOOM_END域腮考。通過在isBabyBoomer方法第一次被調(diào)用的時(shí)候延遲初始化(lazily initializing)(見第71條)這些域,有可能消除這些不必要的初始化工作玄捕,但是不建議這樣做踩蔚。正如延遲初始化(lazy initialization)中常見的情況一樣,這樣做會(huì)使方法的實(shí)現(xiàn)更加復(fù)雜枚粘,從而無法將性能顯著提高到超過已經(jīng)達(dá)到的水平(見第55條)馅闽。
在本條目前面的例子中,所討論到的對象顯然都是能夠被重用的馍迄,因?yàn)樗鼈儽怀跏蓟蟛粫?huì)再改變福也。其他有些情形則并不總是這么明顯了∨嗜Γ考慮適配器(adapter)的情形[Gamma95, p. 139]暴凑,有時(shí)也稱作視圖(view)。適配器是指這樣一個(gè)對象:它把功能委托給一個(gè)后備對象(backing object)赘来,從而為后備對象提供一個(gè)可以替代的接口现喳。由于適配器除了后備對象之外,沒有其他的狀態(tài)信息犬辰,所以針對某個(gè)給定對象的特定適配器而言嗦篱,它不需要?jiǎng)?chuàng)建多個(gè)適配器實(shí)例。
例如忧风,Map接口的keySet方法返回該Map對象的Set視圖默色,其中包含該Map中所有的鍵(key)球凰。粗看起來狮腿,好像每次調(diào)用keySet都應(yīng)該創(chuàng)建一個(gè)新的Set實(shí)例,但是呕诉,對于一個(gè)給定的Map對象缘厢,每次調(diào)用keySet都返回同樣的Set實(shí)例。雖然被返回的Set實(shí)例一般是可改變的甩挫,但是所有返回的對象在功能上是等同的:當(dāng)其中一個(gè)返回對象發(fā)生變化的時(shí)候贴硫,所有其他的返回對象也要發(fā)生變化,因?yàn)樗鼈兪怯赏粋€(gè)Map實(shí)例支撐的。雖然創(chuàng)建keySet視圖對象的多個(gè)實(shí)例并無害處英遭,卻也是沒有必要的间护。
在Java 1.5發(fā)行版本中,有一種創(chuàng)建多余對象的新方法挖诸,稱作自動(dòng)裝箱(autoboxing)汁尺,它允許程序員將基本類型和裝箱基本類型(Boxed Primitive Type)混用,按需要自動(dòng)裝箱和拆箱多律。自動(dòng)裝箱使得基本類型和裝箱基本類型之間的差別變得模糊起來痴突,但是并沒有完全消除。它們在語義上還有著微妙的差別狼荞,在性能上也有著比較明顯的差別(見第49條)辽装。考慮下面的程序相味,它計(jì)算所有int正值的總和拾积。為此,程序必須使用long算法丰涉,因?yàn)閕nt不夠大殷勘,無法容納所有int正值的總和:
這段程序算出的答案是正確的,但是比實(shí)際情況要更慢一些昔搂,只因?yàn)榇蝈e(cuò)了一個(gè)字符玲销。變量sum被聲明成Long而不是long,意味著程序構(gòu)造了大約2的31個(gè)多余的Long實(shí)例(大約每次往Long sum中增加long時(shí)構(gòu)造一個(gè)實(shí)例)摘符。將sum的聲明從Long改成long贤斜,在我的機(jī)器上使運(yùn)行時(shí)間從43秒減少到了6.8秒。結(jié)論很明顯:要優(yōu)先使用基本類型而不是裝箱基本類型逛裤,要當(dāng)心無意識的自動(dòng)裝箱瘩绒。
不要錯(cuò)誤地認(rèn)為本條目所介紹的內(nèi)容暗示著"創(chuàng)建對象的代價(jià)非常昂貴,我們應(yīng)該要盡可能地避免創(chuàng)建對象"带族。相反锁荔,由于小對象的構(gòu)造器只做很少量的顯式工作,所以蝙砌,小對象的創(chuàng)建和回收動(dòng)作是非常廉價(jià)的阳堕,特別是在現(xiàn)代的JVM實(shí)現(xiàn)上更是如此。通過創(chuàng)建附加的對象择克,提升程序的清晰性恬总、簡潔性和功能性,這通常是件好事肚邢。
反之壹堰,通過維護(hù)自己的對象池(object pool)來避免創(chuàng)建對象并不是一種好的做法拭卿,除非池中的對象是非常重量級的。真正正確使用對象池的典型對象示例就是數(shù)據(jù)庫連接池贱纠。建立數(shù)據(jù)庫連接的代價(jià)是非常昂貴的峻厚,因此重用這些對象非常有意義。而且谆焊,數(shù)據(jù)庫的許可可能限制你只能使用一定數(shù)量的連接目木。但是,一般而言懊渡,維護(hù)自己的對象池必定會(huì)把代碼弄得很亂刽射,同時(shí)增加內(nèi)存占用(footprint),并且還會(huì)損害性能√曛矗現(xiàn)代的JVM實(shí)現(xiàn)具有高度優(yōu)化的垃圾回收器誓禁,其性能很容易就會(huì)超過輕量級對象池的性能。
與本條目對應(yīng)的是第39條中有關(guān)"保護(hù)性拷貝(defensive copying)"的內(nèi)容肾档。本條目提及"當(dāng)你應(yīng)該重用現(xiàn)有對象的時(shí)候摹恰,請不要?jiǎng)?chuàng)建新的對象",而第39條則說"當(dāng)你應(yīng)該創(chuàng)建新對象的時(shí)候怒见,請不要重用現(xiàn)有的對象"俗慈。注意,在提倡使用保護(hù)性拷貝的時(shí)候遣耍,因重用對象而付出的代價(jià)要遠(yuǎn)遠(yuǎn)大于因創(chuàng)建重復(fù)對象而付出的代價(jià)闺阱。必要時(shí)如果沒能實(shí)施保護(hù)性拷貝,將會(huì)導(dǎo)致潛在的錯(cuò)誤和安全漏洞舵变;而不必要地創(chuàng)建對象則只會(huì)影響程序的風(fēng)格和性能酣溃。