Effective Java 2.0_中英文對照_Item 5

文章作者:Tyan
博客:noahsnail.com | CSDN | 簡書

Item 5: Avoid creating unnecessary objects

It is often appropriate to reuse a single object instead of creating a new functionally equivalent object each time it is needed. Reuse can be both faster and more stylish. An object can always be reused if it is immutable (Item 15).

每次需要一個對象時,與創(chuàng)建一個新的功能相同的對象相比念逞,復(fù)用一個對象經(jīng)常是合適的。復(fù)用更快更流行馍资。如果一個對象是不變的,那它總是可以復(fù)用融涣。(Item 15)

As an extreme example of what not to do, consider this statement:

下面是一個不該做什么的極端例子:

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

The statement creates a new String instance each time it is executed, and none of those object creations is necessary. The argument to the Stringconstructor ("stringette") is itself a String instance, functionally identical to all of the objects created by the constructor. If this usage occurs in a loop or in a frequently invoked method, millions of String instances can be created needlessly.

這條語句每次執(zhí)行時都會創(chuàng)建一個新的String實(shí)例班套,這些對象的創(chuàng)建都是沒必要的。String構(gòu)造函數(shù)的參數(shù)"stringette"本身就是一個String實(shí)例蛀醉,在功能上與構(gòu)造函數(shù)創(chuàng)建的所有對象都是等價的乞娄。如果這種用法出現(xiàn)在一個循環(huán)或一個頻繁調(diào)用的方法中吆豹,會創(chuàng)建出成千上萬的不必要的String實(shí)例租漂。

The improved version is simply the following:

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

String s = "stringette";

This version uses a single String instance, rather than creating a new one each time it is executed. Furthermore, it is guaranteed that the object will be reused by any other code running in the same virtual machine that happens to contain the same string literal [JLS, 3.10.5].

這個版本使用單個的String實(shí)例,而不是每次執(zhí)行時創(chuàng)建一個新實(shí)例喊儡。此外拨与,它保證了運(yùn)行在虛擬中包含同樣字符串的任何其它代碼都可以復(fù)用這個對象[JLS, 3.10.5]。

You can often avoid creating unnecessary objects by using static factory methods (Item 1) in preference to constructors on immutable classes that provide both. For example, the static factory method Boolean.valueOf(String) is almost always preferable to the constructor Boolean(String). The constructor creates a new object each time it’s called, while the static factory method is never required to do so and won’t in practice.

對于提供了構(gòu)造函數(shù)和靜態(tài)工廠方法的不變類艾猜,使用靜態(tài)工廠方法(Item 1)優(yōu)先于構(gòu)造函數(shù)常陈蛐可以讓你避免創(chuàng)建不必要的對象捻悯。例如,靜態(tài)工廠方法Boolean.valueOf(String)總是優(yōu)先于構(gòu)造函數(shù)Boolean(String)淤毛。每次調(diào)用構(gòu)造函數(shù)都會創(chuàng)建一個新的對象今缚,而靜態(tài)工廠方法從來不要求這樣做,在實(shí)踐中也不會這樣做低淡。

In addition to reusing immutable objects, you can also reuse mutable objects if you know they won’t be modified. Here is a slightly more subtle, and much more common, example of what not to do. It involves mutable Date objects that are never modified once their values have been computed. This class models a person and has an isBabyBoomer method that tells whether the person is a “baby boomer”, in other words, whether the person was born between 1946 and 1964:

除了復(fù)用不可變對象之外姓言,如果你知道可變對象不會被修改,你也可以復(fù)用可變對象蔗蹋。下面是一個比較微妙何荚,更為常見反面例子。它包含可變的Date對象猪杭,這些Date對象一旦計(jì)算出來就不再修改餐塘。這個類對人進(jìn)行了建模,其中有一個isBabyBoomer方法用來區(qū)分這個人是否是一個“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;
    } 
}

The isBabyBoomer method unnecessarily creates a new Calendar, TimeZone, and two Date instances each time it is invoked. The version that follows avoids this inefficiency with a static initializer:

每次調(diào)用時唠倦,isBabyBoomer方法都會創(chuàng)建一個Calendar實(shí)例,一個TimeZone實(shí)例和兩個Date實(shí)例涮较,這是不必要的。下面的版本用靜態(tài)初始化避免了這種低效率的問題:

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

The improved version of the Person class creates Calendar, TimeZone, and Date instances only once, when it is initialized, instead of creating them every time isBabyBoomer is invoked. This results in significant performance gains if the method is invoked frequently. On my machine, the original version takes 32,000 ms for 10 million invocations, while the improved version takes 130 ms, which is about 250 times faster. Not only is performance improved, but so is clarity. Changing boomStart and boomEnd from local variables to static final fields makes it clear that these dates are treated as constants, making the code more understandable. In the interest of full disclosure, the savings from this sort of optimization will not always be this dramatic, as Calendar instances are particularly expensive to create.

Person類的改進(jìn)版本只在初始化時創(chuàng)建Calendar冈止,TimeZoneDate實(shí)例一次狂票,而不是每次調(diào)用isBabyBoomer方法都創(chuàng)建它們。如果isBabyBoomer方法被頻繁調(diào)用的話熙暴,這樣做在性能上會有很大提升闺属。在我的機(jī)器上,最初的版本一千萬次調(diào)用要花費(fèi)32,000毫秒周霉,而改進(jìn)版本只花了130毫秒掂器,比最初版本快了大約250倍。不僅性能改善了俱箱,代碼也更清晰了国瓮。將boomStartboomEnd從局部變量變?yōu)?code>static final字段,很明顯是將它們看作常量狞谱,代碼也更容易理解乃摹。從整體收益來看,這種優(yōu)化的節(jié)約并不總是這么戲劇性的跟衅,因?yàn)?code>Calendar實(shí)例創(chuàng)建的代價是非常昂貴的孵睬。

If the improved version of the Person class is initialized but its isBabyBoomer method is never invoked, the BOOM_START and BOOM_END fields will be initialized unnecessarily. It would be possible to eliminate the unnecessary initializations by lazily initializing these fields (Item 71) the first time the isBabyBoomer method is invoked, but it is not recommended. As is often the case with lazy initialization, it would complicate the implementation and would be unlikely to result in a noticeable performance improvement beyond what we’ve already achieved (Item 55).

如果初始化Person類的改進(jìn)版本,但從不調(diào)用它的isBabyBoomer方法伶跷,BOOM_STARTBOOM_END字段的初始化就是不必要的掰读∶啬可以通過延遲初始化(當(dāng)需要時再初始化)這些字段(Item 71)來消除這些不必要的初始化,當(dāng)?shù)谝淮握{(diào)用isBabyBoomer方法時再進(jìn)行初始化蹈集,但不推薦這樣做烁试。延遲初始化是常有的事,它的實(shí)現(xiàn)是非常復(fù)雜的雾狈,除了我們已有的性能提升之外廓潜,延遲初始化不可能引起明顯的性能提升(Item 55)。

In the previous examples in this item, it was obvious that the objects in question could be reused because they were not modified after initialization. There are other situations where it is less obvious. Consider the case of adapters [Gamma95, p. 139], also known as views. An adapter is an object that delegates to a backing object, providing an alternative interface to the backing object. Because an adapter has no state beyond that of its backing object, there’s no need to create more than one instance of a given adapter to a given object.

在本條目前面的例子中善榛,很明顯問題中的對象可以復(fù)用辩蛋,因?yàn)樗鼈冊诔跏蓟鬀]有被修改。但在其它的情況下它就不那么明顯了移盆〉吭海考慮一個適配器的情況[Gamma95, p. 139],也稱之為視圖咒循。適配器是代理支持對象的對象据途,為支持對象提供了一個可替代的接口。由于適配器除了它的支持對象之外沒有別的狀態(tài)叙甸,因此沒必要創(chuàng)建多個給定對象的適配器實(shí)例颖医。

For example, the keySet method of the Map interface returns a Set view of the Map object, consisting of all the keys in the map. Naively, it would seem that every call to keySet would have to create a new Set instance, but every call to keySet on a given Map object may return the same Set instance. Although the returned Set instance is typically mutable, all of the returned objects are functionally identical: when one of the returned objects changes, so do all the others because they’re all backed by the same Map instance. While it is harmless to create multiple instances of the keySet view object, it is also unnecessary.

例如,Map接口的keySet方法返回一個Map對象的Set視圖裆蒸,包含了map中所有的keys熔萧。乍一看,好像每一次調(diào)用keySet方法都會創(chuàng)建一個新的Set實(shí)例僚祷,但在一個給定的Map對象上每次調(diào)用keySet方法可能返回的都是同一個Set實(shí)例佛致。雖然返回的Set實(shí)例通常都是可變的,但所有的返回對象在功能上是等價的:當(dāng)一個返回對象改變時辙谜,其它的都要改變俺榆,因?yàn)樗鼈兌加赏粋€Map實(shí)例支持。雖然創(chuàng)建多個keySet視圖對象的實(shí)例是無害的装哆,但它是沒必要的罐脊。

There’s a new way to create unnecessary objects in release 1.5. It is called autoboxing, and it allows the programmer to mix primitive and boxed primitive types, boxing and unboxing automatically as needed. Autoboxing blurs but does not erase the distinction between primitive and boxed primitive types. There are subtle semantic distinctions, and not-so-subtle performance differences (Item 49). Consider the following program, which calculates the sum of all the positive int values. To do this, the program has to use long arithmetic, because an int is not big enough to hold the sum of all the positive int values:

在JDK 1.5中有一種新的方式來創(chuàng)建不必要對象。它被稱為自動裝箱烂琴,它允許程序員混合使用基本類型和它們的包裝類型爹殊,JDK會在需要時自動裝箱和拆箱,自動裝箱雖然模糊但不能去除基本類型和包裝類之間的區(qū)別奸绷。它們在語義上有稍微的不同梗夸,但不是輕微的性能差異(Item 49)『抛恚看一下下面的程序反症,計(jì)算所有正數(shù)int值的總和辛块。為了計(jì)算這個,程序必須使用long類型铅碍,因?yàn)?code>int不能容納所有正int值的和:

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

This program gets the right answer, but it is much slower than it should be, due to a one-character typographical error. The variable sum is declared as a Long instead of a long, which means that the program constructs about 2^31 unnecessary Long instances (roughly one for each time the long i is added to the Long sum). Changing the declaration of sum from Long to long reduces the runtime from 43 seconds to 6.8 seconds on my machine. The lesson is clear: prefer primitives to boxed primitives, and watch out for unintentional autoboxing.

這個程序算出了正確答案润绵,但由于一個字符的錯誤,它運(yùn)行的更慢一些胞谈。變量sum聲明為Long而不是long尘盼,這意味著程序構(gòu)建了大約2^31不必要的Long實(shí)例(基本上每次long i加到Long sum上都要創(chuàng)建一個)。將sumLong聲明為long之后烦绳,在我機(jī)器上運(yùn)行時間從43秒降到了6.8秒卿捎。結(jié)論很明顯:使用基本類型優(yōu)先于包裝類,當(dāng)心無意的自動裝箱径密。

This item should not be misconstrued to imply that object creation is expensive and should be avoided. On the contrary, the creation and reclamation of small objects whose constructors do little explicit work is cheap, especially on modern JVM implementations. Creating additional objects to enhance the clarity, simplicity, or power of a program is generally a good thing.

不該將本條目誤解成暗示創(chuàng)建對象是昂貴的午阵,應(yīng)該避免創(chuàng)建對象。恰恰相反享扔,創(chuàng)建和回收構(gòu)造函數(shù)做很少顯式工作的小對象是非常廉價的底桂,尤其是在現(xiàn)代的JVM實(shí)現(xiàn)上。創(chuàng)建額外的對象來增強(qiáng)程序的清晰性惧眠,簡潔性籽懦,或能力通常是一件好事。

Conversely, avoiding object creation by maintaining your own object pool is a bad idea unless the objects in the pool are extremely heavyweight. The classic example of an object that does justify an object pool is a database connection. The cost of establishing the connection is sufficiently high that it makes sense to reuse these objects. Also, your database license may limit you to a fixed number of connections. Generally speaking, however, maintaining your own object pools clutters your code, increases memory footprint, and harms performance. Modern JVM implementations have highly optimized garbage collectors that easily outperform such object pools on lightweight objects.

相反的氛魁,通過維護(hù)你自己的對象池來避免創(chuàng)建對象是一個壞主意猫十,除非對象池中的對象是極度重量級的。真正證明對象池的對象經(jīng)典例子是數(shù)據(jù)庫連接呆盖。建立連接的代價是非常大的,因此復(fù)用這些對象是很有意義的贷笛。數(shù)據(jù)庫許可可能也限制你使用固定數(shù)目的連接应又。但是,通常來說維護(hù)你自己的對象池會使你的代碼很亂乏苦,增加內(nèi)存占用株扛,而且損害性能。現(xiàn)代JVM實(shí)現(xiàn)有高度優(yōu)化的垃圾回收機(jī)制汇荐,維護(hù)輕量級對象很容易比對象池做的更好洞就。

The counterpoint to this item is Item 39 on defensive copying. Item 5 says, “Don’t create a new object when you should reuse an existing one,” while Item 39 says, “Don’t reuse an existing object when you should create a new one.” Note that the penalty for reusing an object when defensive copying is called for is far greater than the penalty for needlessly creating a duplicate object. Failing to make defensive copies where required can lead to insidious bugs and security holes; creating objects unnecessarily merely affects style and performance.

與本條目對應(yīng)的是Item 39 保護(hù)性拷貝。Item 5 聲稱掀淘,『不要創(chuàng)建一個新的對象旬蟋,當(dāng)你應(yīng)該復(fù)用一個現(xiàn)有的對象時』,而Item 39 聲稱革娄,『不要重用一個現(xiàn)有的對象倾贰,當(dāng)你應(yīng)該創(chuàng)建一個新的對象時』冕碟。注意,當(dāng)保護(hù)性拷貝時復(fù)用一個對象的代價要遠(yuǎn)大于創(chuàng)建一個不必要的重復(fù)對象的代價匆浙。當(dāng)需要時沒有創(chuàng)建一個保護(hù)性拷貝可能導(dǎo)致潛在的錯誤和安全漏洞安寺;創(chuàng)建不必要的對象只會影響程序風(fēng)格及性能。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末首尼,一起剝皮案震驚了整個濱河市挑庶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌软能,老刑警劉巖迎捺,帶你破解...
    沈念sama閱讀 211,496評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異埋嵌,居然都是意外死亡破加,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,187評論 3 385
  • 文/潘曉璐 我一進(jìn)店門雹嗦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來范舀,“玉大人,你說我怎么就攤上這事了罪《Щ罚” “怎么了?”我有些...
    開封第一講書人閱讀 157,091評論 0 348
  • 文/不壞的土叔 我叫張陵泊藕,是天一觀的道長辅辩。 經(jīng)常有香客問我,道長娃圆,這世上最難降的妖魔是什么玫锋? 我笑而不...
    開封第一講書人閱讀 56,458評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮讼呢,結(jié)果婚禮上撩鹿,老公的妹妹穿的比我還像新娘。我一直安慰自己悦屏,他們只是感情好节沦,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,542評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著础爬,像睡著了一般甫贯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上看蚜,一...
    開封第一講書人閱讀 49,802評論 1 290
  • 那天叫搁,我揣著相機(jī)與錄音,去河邊找鬼。 笑死常熙,一個胖子當(dāng)著我的面吹牛纬乍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播裸卫,決...
    沈念sama閱讀 38,945評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼仿贬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了墓贿?” 一聲冷哼從身側(cè)響起茧泪,我...
    開封第一講書人閱讀 37,709評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎聋袋,沒想到半個月后队伟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,158評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡幽勒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,502評論 2 327
  • 正文 我和宋清朗相戀三年嗜侮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啥容。...
    茶點(diǎn)故事閱讀 38,637評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡锈颗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出咪惠,到底是詐尸還是另有隱情击吱,我是刑警寧澤,帶...
    沈念sama閱讀 34,300評論 4 329
  • 正文 年R本政府宣布遥昧,位于F島的核電站覆醇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏炭臭。R本人自食惡果不足惜永脓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,911評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鞋仍。 院中可真熱鬧憨奸,春花似錦、人聲如沸凿试。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,744評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽那婉。三九已至,卻和暖如春党瓮,著一層夾襖步出監(jiān)牢的瞬間详炬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,982評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留呛谜,地道東北人在跳。 一個月前我還...
    沈念sama閱讀 46,344評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像隐岛,于是被迫代替她去往敵國和親猫妙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,500評論 2 348

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