成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

第6項:避免創(chuàng)建不需要的對象

Amio / 2092人閱讀

摘要:傳遞給構(gòu)造器的參數(shù)本身就是一個實例,功能方面等同于構(gòu)造器創(chuàng)建的所有對象。對于同時提供了靜態(tài)工廠方法第項和構(gòu)造器的不可變類,通??梢允褂渺o態(tài)工廠方法而不是構(gòu)造器,這樣可以經(jīng)常避免創(chuàng)建不必要的對象。

??一般來說,最好能重用對象而不是在每次需要的時候就創(chuàng)建一個相同功能的新對象。重用的方式既快速,有流行。如果對象是不可變(immutable)的(第17項),那么就能重復(fù)使用它。

??作為一個極端的反面例子,考慮下面的語句:

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

??該語句每次被執(zhí)行的時候都創(chuàng)建一個新的String實例,但是這些對象的創(chuàng)建并不都是必要的。傳遞給String構(gòu)造器的參數(shù)("bikini")本身就是一個String實例,功能方面等同于構(gòu)造器創(chuàng)建的所有對象。如果這種方法是用在一個循環(huán)中,或者是在一個被頻繁調(diào)用的方法中,就會創(chuàng)建出成千上萬的不必要的String實例。

??改進(jìn)后的版本如下:

String s = "bikini";

??這個版本只用了一個String實例,而不是每次執(zhí)行時都創(chuàng)建一個新的實例。除此之外,它可以保證,對于所有在同一臺虛擬機中運行的代碼,只要它們包含相同的字符串字面常量,該對象就會被重用 [JLS, 3.10.5]。

??對于同時提供了靜態(tài)工廠方法(第1項)和構(gòu)造器的不可變類,通??梢允褂渺o態(tài)工廠方法而不是構(gòu)造器,這樣可以經(jīng)常避免創(chuàng)建不必要的對象。例如,這個靜態(tài)工廠方法Boolean.valueOf(String)總是優(yōu)先于在Java 9中拋棄的構(gòu)造器 Boolean(String)。構(gòu)造函數(shù)必須在每次調(diào)用時創(chuàng)建一個新對象,而工廠方法從不需要這樣做,也不會在實踐中。除了重用不可變對象之外,如果你知道它們不會被修改,你還可以重用可變對象。

??有些對象的創(chuàng)建比其他對象的代價大,如果你需要反復(fù)創(chuàng)建這種代價大的對象,建議將其緩存起來以便重復(fù)使用。不幸的是,當(dāng)你創(chuàng)建這樣一個對象時,并不總是很明顯。假設(shè)你想編寫一個方法來確定一個字符串是否是一個有效的羅馬數(shù)字。使用正則表達(dá)式是最簡單的方法:

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

??此實現(xiàn)的問題在于它依賴于String.matches方法。雖然String.matches是檢查字符串是否與正則表達(dá)式匹配的最簡單方法,但它不適合在性能關(guān)鍵的情況下重復(fù)使用。問題是它在內(nèi)部為正則表達(dá)式創(chuàng)建了一個Pattern實例,并且只使用它一次,之后它就可能會被垃圾回收機制回收。創(chuàng)建Pattern實例的代價很大,因為它需要將正則表達(dá)式編譯為有限狀態(tài)機(because it requires compiling the regular expression into a finite state machine)。

??為了提高性能,將正則表達(dá)式顯式編譯為Pattern實例(不可變)作為類初始化的一部分,對其進(jìn)行緩存,并在每次調(diào)用isRomanNumeral方法時重用相同的實例:

// 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可以顯著提高性能。在我的機器上,原始版本在8個字符的輸入字符串上需要1.1μs,而改進(jìn)版本需要0.17μs,這是6.5倍的速度。不僅提高了性能,而且功能更加明了。為不可見的Pattern實例創(chuàng)建一個靜態(tài)的final字段,我們可以給它一個名字,它比正則表達(dá)式本身更具有可讀性。

??如果初始化包含改進(jìn)版本的isRimanNumberal方法的類時,但是從不調(diào)用該方法,則不需要初始化字段ROMAN。在第一次調(diào)用isRimanNumberal方法時,可以通過延遲初始化字段(第83項)來消除使用時未初始化的影響,但不建議這樣做。延遲初始化的做法通常都有一個情況,那就是它會把實現(xiàn)復(fù)雜化,從而導(dǎo)致無法測試它的性能改進(jìn)情況。

??當(dāng)一個對象是不可變的,那么就可以安全地重用它,但是在其他情況下,它并不是那么明顯,甚至違反直覺。這時候可以考慮使用適配器 [Gamma95],也稱為視圖。適配器是委托給支持對象的對象(An adapter is an object that delegates to a backing object),它提供一個備用接口。因為適配器的狀態(tài)不超過其支持對象的狀態(tài),所以不需要為給定對象創(chuàng)建一個給定適配器的多個實例。

??例如,Map接口的keySet方法返回Map對象的Set視圖,該視圖由Map中的所有鍵組成。看起來,似乎每次調(diào)用keySet都必須創(chuàng)建一個新的Set實例,但是對給定Map對象上的keySet的每次調(diào)用都可能返回相同的Set實例。盡管返回的Set實例通常是可變的,但所有返回的對象在功能上都是相同的:當(dāng)其中一個返回的對象發(fā)生更改時,所有其他對象也會發(fā)生更改,因為它們都由相同的Map實例支持。雖然創(chuàng)建keySet視圖對象的多個實例在很大程度上是無害的,但不必要這樣做,并且這樣做沒有任何好處。

??創(chuàng)建不必要的對象的另一種方式是自動裝箱,它允許程序猿將基本類型和裝箱基本類型(Boxed Primitive Type)混用,按需自動裝箱和拆箱。自動裝箱使得基本類型和裝箱基本類型之間的差別變得模糊起來,但是并沒有完全消除。它們在語義上還有微妙的差別,在性能上也有著比較明顯的差別(第61項)。請考慮以下方法,該方法計算所有正整數(shù)值的總和,為此,程序必須使用long類型,因為int類型無法容納所有正整數(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;
}

??這段程序算出的答案是正確的,但是比實際情況要更慢一些,只因為錯打了一個字符。變量sum被聲明成Long而不是long,意味著程序構(gòu)造了大約 2^31 個多余的Long實例(大約每次往Long sum中增加long時構(gòu)造一個實例)。將sum的聲明從Long改成long,在我的機器上運行時間從43秒減少到了6秒。結(jié)論很明顯:要優(yōu)先使用基本類型而不是裝箱基本類型,要當(dāng)心無意識的自動裝箱。

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

??反之,通過維護自己的對象池(object pool)來避免創(chuàng)建對象并不是一種好的做法,除非池中的對象是非常重量級的。真正正確使用對象池的經(jīng)典對象示例就是數(shù)據(jù)庫連接池。建立數(shù)據(jù)庫連接的代價是非常昂貴的,因此重用這些對象非常有意義。但是,通常來說,維護自己的對象池必定會把代碼弄得很亂,同時增加內(nèi)存占用,而且會影響性能。現(xiàn)代的JVM實現(xiàn)具有高度優(yōu)化的垃圾回收器,其性能很容易就會超過輕量級對象池的性能。

??與本項對應(yīng)的是第50項的“保護性拷貝”的內(nèi)容。該項說得是:你應(yīng)該重用已經(jīng)存在的對象,而不是去創(chuàng)建一個新的對象。然而第50項說的是:你應(yīng)該創(chuàng)建一個新的對象而不是重用一個已經(jīng)存在的對象。注意,在提倡使用保護性拷貝的時候,因重用對象而付出的代價要遠(yuǎn)遠(yuǎn)大于因創(chuàng)建重復(fù)對象而付出的代價。必要時如果沒能實施保護性拷貝,將會導(dǎo)致潛在的錯誤和安全漏洞,而不必要地創(chuàng)建對象則只會影響程序的風(fēng)格和性能。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/77469.html

相關(guān)文章

  • Effective Java 三版 全文翻譯

    摘要:本章中的大部分內(nèi)容適用于構(gòu)造函數(shù)和方法。第項其他方法優(yōu)先于序列化第項謹(jǐn)慎地實現(xiàn)接口第項考慮使用自定義的序列化形式第項保護性地編寫方法第項對于實例控制,枚舉類型優(yōu)先于第項考慮用序列化代理代替序列化實例附錄與第版中項目的對應(yīng)關(guān)系參考文獻(xiàn) effective-java-third-edition 介紹 Effective Java 第三版全文翻譯,純屬個人業(yè)余翻譯,不合理的地方,望指正,感激...

    galois 評論0 收藏0
  • Effective Java 3rd.Edition 翻譯

    摘要:推薦序前言致謝第一章引言第二章創(chuàng)建和銷毀對象第項用靜態(tài)工廠方法代替構(gòu)造器第項遇到多個構(gòu)造器參數(shù)時要考慮使用構(gòu)建器第項用私有構(gòu)造器或者枚舉類型強化屬性第項通過私有構(gòu)造器強化不可實例化的能力第項優(yōu)先考慮依賴注入來引用資源第項避免創(chuàng)建不必要的對象 推薦序 前言 致謝 第一章 引言 第二章 創(chuàng)建和銷毀對象 第1項:用靜態(tài)工廠方法代替構(gòu)造器 第2項:遇到多個構(gòu)造器參數(shù)時要考慮使用構(gòu)建器 第...

    KoreyLee 評論0 收藏0
  • 二章 創(chuàng)建和銷毀對象

    摘要:一個類可以提供一個公共靜態(tài)工廠方法,它僅僅是一第項遇到多個構(gòu)造器參數(shù)時要考慮使用構(gòu)建器靜態(tài)工廠和構(gòu)造器有個共同的局限性他們都不能很好地擴展到大量的可選參數(shù)。 ??本章涉及創(chuàng)建和銷毀對象,包括何時以及如何創(chuàng)建它們,何時以及如何避免創(chuàng)建它們,如何確保它們被及時銷毀,以及如何管理在銷毀之前必須進(jìn)行的清理操作。 第1項:用靜態(tài)工廠方法代替構(gòu)造器 ??類允許客戶端獲取實例的傳統(tǒng)方法是提供公共構(gòu)造...

    Jeffrrey 評論0 收藏0
  • 1:考慮靜態(tài)工廠方法而是構(gòu)造函數(shù)

    摘要:提供靜態(tài)工廠方法而不是公共構(gòu)造函數(shù)既有優(yōu)點也有缺點。它們不像構(gòu)造函數(shù)那樣在文檔中脫穎而出,因此很難弄清楚如何實例化提供靜態(tài)工廠方法而不是構(gòu)造函數(shù)的類。 ??類允許客戶端獲取實例的傳統(tǒng)方法是提供公共構(gòu)造器。還有一種技術(shù)應(yīng)該是每個程序員的工具箱的一部分。一個類可以提供一個公共靜態(tài)工廠方法,它僅僅是一個返回類實例的靜態(tài)方法。下面是布爾(布爾型的盒裝原語類)的一個簡單示例。這個方法將一個布爾原...

    趙連江 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<