摘要:傳遞給構(gòu)造器的參數(shù)本身就是一個(gè)實(shí)例,功能方面等同于構(gòu)造器創(chuàng)建的所有對(duì)象。對(duì)于同時(shí)提供了靜態(tài)工廠方法第項(xiàng)和構(gòu)造器的不可變類,通常可以使用靜態(tài)工廠方法而不是構(gòu)造器,這樣可以經(jīng)常避免創(chuàng)建不必要的對(duì)象。
??一般來說,最好能重用對(duì)象而不是在每次需要的時(shí)候就創(chuàng)建一個(gè)相同功能的新對(duì)象。重用的方式既快速,有流行。如果對(duì)象是不可變(immutable)的(第17項(xiàng)),那么就能重復(fù)使用它。
??作為一個(gè)極端的反面例子,考慮下面的語(yǔ)句:
String s = new String("bikini"); // DON"T DO THIS!
??該語(yǔ)句每次被執(zhí)行的時(shí)候都創(chuàng)建一個(gè)新的String實(shí)例,但是這些對(duì)象的創(chuàng)建并不都是必要的。傳遞給String構(gòu)造器的參數(shù)("bikini")本身就是一個(gè)String實(shí)例,功能方面等同于構(gòu)造器創(chuàng)建的所有對(duì)象。如果這種方法是用在一個(gè)循環(huán)中,或者是在一個(gè)被頻繁調(diào)用的方法中,就會(huì)創(chuàng)建出成千上萬(wàn)的不必要的String實(shí)例。
??改進(jìn)后的版本如下:
String s = "bikini";
??這個(gè)版本只用了一個(gè)String實(shí)例,而不是每次執(zhí)行時(shí)都創(chuàng)建一個(gè)新的實(shí)例。除此之外,它可以保證,對(duì)于所有在同一臺(tái)虛擬機(jī)中運(yùn)行的代碼,只要它們包含相同的字符串字面常量,該對(duì)象就會(huì)被重用 [JLS, 3.10.5]。
??對(duì)于同時(shí)提供了靜態(tài)工廠方法(第1項(xiàng))和構(gòu)造器的不可變類,通常可以使用靜態(tài)工廠方法而不是構(gòu)造器,這樣可以經(jīng)常避免創(chuàng)建不必要的對(duì)象。例如,這個(gè)靜態(tài)工廠方法Boolean.valueOf(String)總是優(yōu)先于在Java 9中拋棄的構(gòu)造器 Boolean(String)。構(gòu)造函數(shù)必須在每次調(diào)用時(shí)創(chuàng)建一個(gè)新對(duì)象,而工廠方法從不需要這樣做,也不會(huì)在實(shí)踐中。除了重用不可變對(duì)象之外,如果你知道它們不會(huì)被修改,你還可以重用可變對(duì)象。
??有些對(duì)象的創(chuàng)建比其他對(duì)象的代價(jià)大,如果你需要反復(fù)創(chuàng)建這種代價(jià)大的對(duì)象,建議將其緩存起來以便重復(fù)使用。不幸的是,當(dāng)你創(chuàng)建這樣一個(gè)對(duì)象時(shí),并不總是很明顯。假設(shè)你想編寫一個(gè)方法來確定一個(gè)字符串是否是一個(gè)有效的羅馬數(shù)字。使用正則表達(dá)式是最簡(jiǎn)單的方法:
// Performance can be greatly improved! 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})$"); }
??此實(shí)現(xiàn)的問題在于它依賴于String.matches方法。雖然String.matches是檢查字符串是否與正則表達(dá)式匹配的最簡(jiǎn)單方法,但它不適合在性能關(guān)鍵的情況下重復(fù)使用。問題是它在內(nèi)部為正則表達(dá)式創(chuàng)建了一個(gè)Pattern實(shí)例,并且只使用它一次,之后它就可能會(huì)被垃圾回收機(jī)制回收。創(chuàng)建Pattern實(shí)例的代價(jià)很大,因?yàn)樗枰獙⒄齽t表達(dá)式編譯為有限狀態(tài)機(jī)(because it requires compiling the regular expression into a finite state machine)。
??為了提高性能,將正則表達(dá)式顯式編譯為Pattern實(shí)例(不可變)作為類初始化的一部分,對(duì)其進(jìn)行緩存,并在每次調(diào)用isRomanNumeral方法時(shí)重用相同的實(shí)例:
// Reusing expensive object for improved performance 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(); } }
??如果經(jīng)常調(diào)用的話,改進(jìn)版本的isRomanNumeral可以顯著提高性能。在我的機(jī)器上,原始版本在8個(gè)字符的輸入字符串上需要1.1μs,而改進(jìn)版本需要0.17μs,這是6.5倍的速度。不僅提高了性能,而且功能更加明了。為不可見的Pattern實(shí)例創(chuàng)建一個(gè)靜態(tài)的final字段,我們可以給它一個(gè)名字,它比正則表達(dá)式本身更具有可讀性。
??如果初始化包含改進(jìn)版本的isRimanNumberal方法的類時(shí),但是從不調(diào)用該方法,則不需要初始化字段ROMAN。在第一次調(diào)用isRimanNumberal方法時(shí),可以通過延遲初始化字段(第83項(xiàng))來消除使用時(shí)未初始化的影響,但不建議這樣做。延遲初始化的做法通常都有一個(gè)情況,那就是它會(huì)把實(shí)現(xiàn)復(fù)雜化,從而導(dǎo)致無(wú)法測(cè)試它的性能改進(jìn)情況。
??當(dāng)一個(gè)對(duì)象是不可變的,那么就可以安全地重用它,但是在其他情況下,它并不是那么明顯,甚至違反直覺。這時(shí)候可以考慮使用適配器 [Gamma95],也稱為視圖。適配器是委托給支持對(duì)象的對(duì)象(An adapter is an object that delegates to a backing object),它提供一個(gè)備用接口。因?yàn)檫m配器的狀態(tài)不超過其支持對(duì)象的狀態(tài),所以不需要為給定對(duì)象創(chuàng)建一個(gè)給定適配器的多個(gè)實(shí)例。
??例如,Map接口的keySet方法返回Map對(duì)象的Set視圖,該視圖由Map中的所有鍵組成。看起來,似乎每次調(diào)用keySet都必須創(chuàng)建一個(gè)新的Set實(shí)例,但是對(duì)給定Map對(duì)象上的keySet的每次調(diào)用都可能返回相同的Set實(shí)例。盡管返回的Set實(shí)例通常是可變的,但所有返回的對(duì)象在功能上都是相同的:當(dāng)其中一個(gè)返回的對(duì)象發(fā)生更改時(shí),所有其他對(duì)象也會(huì)發(fā)生更改,因?yàn)樗鼈兌加上嗤腗ap實(shí)例支持。雖然創(chuàng)建keySet視圖對(duì)象的多個(gè)實(shí)例在很大程度上是無(wú)害的,但不必要這樣做,并且這樣做沒有任何好處。
??創(chuàng)建不必要的對(duì)象的另一種方式是自動(dòng)裝箱,它允許程序猿將基本類型和裝箱基本類型(Boxed Primitive Type)混用,按需自動(dòng)裝箱和拆箱。自動(dòng)裝箱使得基本類型和裝箱基本類型之間的差別變得模糊起來,但是并沒有完全消除。它們?cè)谡Z(yǔ)義上還有微妙的差別,在性能上也有著比較明顯的差別(第61項(xiàng))。請(qǐng)考慮以下方法,該方法計(jì)算所有正整數(shù)值的總和,為此,程序必須使用long類型,因?yàn)閕nt類型無(wú)法容納所有正整數(shù)的總和:
// Hideously slow! Can you spot the object creation? private static long sum() { Long sum = 0L; for (long i = 0; i <= Integer.MAX_VALUE; i++) sum += i; return sum; }
??這段程序算出的答案是正確的,但是比實(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秒。結(jié)論很明顯:要優(yōu)先使用基本類型而不是裝箱基本類型,要當(dāng)心無(wú)意識(shí)的自動(dòng)裝箱。
??不要錯(cuò)誤地認(rèn)為本項(xiàng)所介紹的內(nèi)容暗示著“創(chuàng)建對(duì)象的代價(jià)非常昂貴,我們就應(yīng)該盡可能地避免創(chuàng)建對(duì)象”。相反,由于小對(duì)象的構(gòu)造器只做很少量的顯示工作,所以,小對(duì)象的創(chuàng)建和回收動(dòng)作是非常廉價(jià)的,特別是在現(xiàn)代的JVM實(shí)現(xiàn)上更是如此。通過創(chuàng)建附加的對(duì)象,提升程序的清晰性、簡(jiǎn)潔性和功能性,這通常是件好事。
??反之,通過維護(hù)自己的對(duì)象池(object pool)來避免創(chuàng)建對(duì)象并不是一種好的做法,除非池中的對(duì)象是非常重量級(jí)的。真正正確使用對(duì)象池的經(jīng)典對(duì)象示例就是數(shù)據(jù)庫(kù)連接池。建立數(shù)據(jù)庫(kù)連接的代價(jià)是非常昂貴的,因此重用這些對(duì)象非常有意義。但是,通常來說,維護(hù)自己的對(duì)象池必定會(huì)把代碼弄得很亂,同時(shí)增加內(nèi)存占用,而且會(huì)影響性能。現(xiàn)代的JVM實(shí)現(xiàn)具有高度優(yōu)化的垃圾回收器,其性能很容易就會(huì)超過輕量級(jí)對(duì)象池的性能。
??與本項(xiàng)對(duì)應(yīng)的是第50項(xiàng)的“保護(hù)性拷貝”的內(nèi)容。該項(xiàng)說得是:你應(yīng)該重用已經(jīng)存在的對(duì)象,而不是去創(chuàng)建一個(gè)新的對(duì)象。然而第50項(xiàng)說的是:你應(yīng)該創(chuàng)建一個(gè)新的對(duì)象而不是重用一個(gè)已經(jīng)存在的對(duì)象。注意,在提倡使用保護(hù)性拷貝的時(shí)候,因重用對(duì)象而付出的代價(jià)要遠(yuǎn)遠(yuǎn)大于因創(chuàng)建重復(fù)對(duì)象而付出的代價(jià)。必要時(shí)如果沒能實(shí)施保護(hù)性拷貝,將會(huì)導(dǎo)致潛在的錯(cuò)誤和安全漏洞,而不必要地創(chuàng)建對(duì)象則只會(huì)影響程序的風(fēng)格和性能。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/77469.html
摘要:本章中的大部分內(nèi)容適用于構(gòu)造函數(shù)和方法。第項(xiàng)其他方法優(yōu)先于序列化第項(xiàng)謹(jǐn)慎地實(shí)現(xiàn)接口第項(xiàng)考慮使用自定義的序列化形式第項(xiàng)保護(hù)性地編寫方法第項(xiàng)對(duì)于實(shí)例控制,枚舉類型優(yōu)先于第項(xiàng)考慮用序列化代理代替序列化實(shí)例附錄與第版中項(xiàng)目的對(duì)應(yīng)關(guān)系參考文獻(xiàn) effective-java-third-edition 介紹 Effective Java 第三版全文翻譯,純屬個(gè)人業(yè)余翻譯,不合理的地方,望指正,感激...
摘要:推薦序前言致謝第一章引言第二章創(chuàng)建和銷毀對(duì)象第項(xiàng)用靜態(tài)工廠方法代替構(gòu)造器第項(xiàng)遇到多個(gè)構(gòu)造器參數(shù)時(shí)要考慮使用構(gòu)建器第項(xiàng)用私有構(gòu)造器或者枚舉類型強(qiáng)化屬性第項(xiàng)通過私有構(gòu)造器強(qiáng)化不可實(shí)例化的能力第項(xiàng)優(yōu)先考慮依賴注入來引用資源第項(xiàng)避免創(chuàng)建不必要的對(duì)象 推薦序 前言 致謝 第一章 引言 第二章 創(chuàng)建和銷毀對(duì)象 第1項(xiàng):用靜態(tài)工廠方法代替構(gòu)造器 第2項(xiàng):遇到多個(gè)構(gòu)造器參數(shù)時(shí)要考慮使用構(gòu)建器 第...
摘要:一個(gè)類可以提供一個(gè)公共靜態(tài)工廠方法,它僅僅是一第項(xiàng)遇到多個(gè)構(gòu)造器參數(shù)時(shí)要考慮使用構(gòu)建器靜態(tài)工廠和構(gòu)造器有個(gè)共同的局限性他們都不能很好地?cái)U(kuò)展到大量的可選參數(shù)。 ??本章涉及創(chuàng)建和銷毀對(duì)象,包括何時(shí)以及如何創(chuàng)建它們,何時(shí)以及如何避免創(chuàng)建它們,如何確保它們被及時(shí)銷毀,以及如何管理在銷毀之前必須進(jìn)行的清理操作。 第1項(xiàng):用靜態(tài)工廠方法代替構(gòu)造器 ??類允許客戶端獲取實(shí)例的傳統(tǒng)方法是提供公共構(gòu)造...
摘要:提供靜態(tài)工廠方法而不是公共構(gòu)造函數(shù)既有優(yōu)點(diǎn)也有缺點(diǎn)。它們不像構(gòu)造函數(shù)那樣在文檔中脫穎而出,因此很難弄清楚如何實(shí)例化提供靜態(tài)工廠方法而不是構(gòu)造函數(shù)的類。 ??類允許客戶端獲取實(shí)例的傳統(tǒng)方法是提供公共構(gòu)造器。還有一種技術(shù)應(yīng)該是每個(gè)程序員的工具箱的一部分。一個(gè)類可以提供一個(gè)公共靜態(tài)工廠方法,它僅僅是一個(gè)返回類實(shí)例的靜態(tài)方法。下面是布爾(布爾型的盒裝原語(yǔ)類)的一個(gè)簡(jiǎn)單示例。這個(gè)方法將一個(gè)布爾原...
閱讀 2485·2023-04-25 21:41
閱讀 1657·2021-09-22 15:17
閱讀 1928·2021-09-22 10:02
閱讀 2443·2021-09-10 11:21
閱讀 2585·2019-08-30 15:53
閱讀 1004·2019-08-30 15:44
閱讀 957·2019-08-30 13:46
閱讀 1146·2019-08-29 18:36