文章作者:Tyan
博客:noahsnail.com | CSDN | 簡(jiǎn)書
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).
每次需要一個(gè)對(duì)象時(shí),與創(chuàng)建一個(gè)新的功能相同的對(duì)象相比,復(fù)用一個(gè)對(duì)象經(jīng)常是合適的。復(fù)用更快更流行。如果一個(gè)對(duì)象是不變的,那它總是可以復(fù)用。(Item 15)
As an extreme example of what not to do, consider this statement:
下面是一個(gè)不該做什么的極端例子:
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.
這條語(yǔ)句每次執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)新的String實(shí)例,這些對(duì)象的創(chuàng)建都是沒必要的。String構(gòu)造函數(shù)的參數(shù)"stringette"本身就是一個(gè)String實(shí)例,在功能上與構(gòu)造函數(shù)創(chuàng)建的所有對(duì)象都是等價(jià)的。如果這種用法出現(xiàn)在一個(gè)循環(huán)或一個(gè)頻繁調(diào)用的方法中,會(huì)創(chuàng)建出成千上萬(wàn)的不必要的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].
這個(gè)版本使用單個(gè)的String實(shí)例,而不是每次執(zhí)行時(shí)創(chuàng)建一個(gè)新實(shí)例。此外,它保證了運(yùn)行在虛擬中包含同樣字符串的任何其它代碼都可以復(fù)用這個(gè)對(duì)象[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.
對(duì)于提供了構(gòu)造函數(shù)和靜態(tài)工廠方法的不變類,使用靜態(tài)工廠方法(Item 1)優(yōu)先于構(gòu)造函數(shù)常??梢宰屇惚苊鈩?chuàng)建不必要的對(duì)象。例如,靜態(tài)工廠方法Boolean.valueOf(String)總是優(yōu)先于構(gòu)造函數(shù)Boolean(String)。每次調(diào)用構(gòu)造函數(shù)都會(huì)創(chuàng)建一個(gè)新的對(duì)象,而靜態(tài)工廠方法從來(lái)不要求這樣做,在實(shí)踐中也不會(huì)這樣做。
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ù)用不可變對(duì)象之外,如果你知道可變對(duì)象不會(huì)被修改,你也可以復(fù)用可變對(duì)象。下面是一個(gè)比較微妙,更為常見反面例子。它包含可變的Date對(duì)象,這些Date對(duì)象一旦計(jì)算出來(lái)就不再修改。這個(gè)類對(duì)人進(jìn)行了建模,其中有一個(gè)isBabyBoomer方法用來(lái)區(qū)分這個(gè)人是否是一個(gè)“baby boomer(生育高峰時(shí)的小孩)”,換句話說(shuō)就是判斷這個(gè)人是否出生在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)用時(shí),isBabyBoomer方法都會(huì)創(chuàng)建一個(gè)Calendar實(shí)例,一個(gè)TimeZone實(shí)例和兩個(gè)Date實(shí)例,這是不必要的。下面的版本用靜態(tài)初始化避免了這種低效率的問(wèn)題:
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)版本只在初始化時(shí)創(chuàng)建Calendar,TimeZone和Date實(shí)例一次,而不是每次調(diào)用isBabyBoomer方法都創(chuàng)建它們。如果isBabyBoomer方法被頻繁調(diào)用的話,這樣做在性能上會(huì)有很大提升。在我的機(jī)器上,最初的版本一千萬(wàn)次調(diào)用要花費(fèi)32,000毫秒,而改進(jìn)版本只花了130毫秒,比最初版本快了大約250倍。不僅性能改善了,代碼也更清晰了。將boomStart和boomEnd從局部變量變?yōu)?code>static final字段,很明顯是將它們看作常量,代碼也更容易理解。從整體收益來(lái)看,這種優(yōu)化的節(jié)約并不總是這么戲劇性的,因?yàn)?code>Calendar實(shí)例創(chuàng)建的代價(jià)是非常昂貴的。
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_START和BOOM_END字段的初始化就是不必要的??梢酝ㄟ^(guò)延遲初始化(當(dāng)需要時(shí)再初始化)這些字段(Item 71)來(lái)消除這些不必要的初始化,當(dāng)?shù)谝淮握{(diào)用isBabyBoomer方法時(shí)再進(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.
在本條目前面的例子中,很明顯問(wèn)題中的對(duì)象可以復(fù)用,因?yàn)樗鼈冊(cè)诔跏蓟鬀]有被修改。但在其它的情況下它就不那么明顯了??紤]一個(gè)適配器的情況[Gamma95, p. 139],也稱之為視圖。適配器是代理支持對(duì)象的對(duì)象,為支持對(duì)象提供了一個(gè)可替代的接口。由于適配器除了它的支持對(duì)象之外沒有別的狀態(tài),因此沒必要?jiǎng)?chuàng)建多個(gè)給定對(duì)象的適配器實(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方法返回一個(gè)Map對(duì)象的Set視圖,包含了map中所有的keys。乍一看,好像每一次調(diào)用keySet方法都會(huì)創(chuàng)建一個(gè)新的Set實(shí)例,但在一個(gè)給定的Map對(duì)象上每次調(diào)用keySet方法可能返回的都是同一個(gè)Set實(shí)例。雖然返回的Set實(shí)例通常都是可變的,但所有的返回對(duì)象在功能上是等價(jià)的:當(dāng)一個(gè)返回對(duì)象改變時(shí),其它的都要改變,因?yàn)樗鼈兌加赏粋€(gè)Map實(shí)例支持。雖然創(chuàng)建多個(gè)keySet視圖對(duì)象的實(shí)例是無(wú)害的,但它是沒必要的。
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中有一種新的方式來(lái)創(chuàng)建不必要對(duì)象。它被稱為自動(dòng)裝箱,它允許程序員混合使用基本類型和它們的包裝類型,JDK會(huì)在需要時(shí)自動(dòng)裝箱和拆箱,自動(dòng)裝箱雖然模糊但不能去除基本類型和包裝類之間的區(qū)別。它們?cè)谡Z(yǔ)義上有稍微的不同,但不是輕微的性能差異(Item 49)。看一下下面的程序,計(jì)算所有正數(shù)int值的總和。為了計(jì)算這個(gè),程序必須使用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.
這個(gè)程序算出了正確答案,但由于一個(gè)字符的錯(cuò)誤,它運(yùn)行的更慢一些。變量sum聲明為Long而不是long,這意味著程序構(gòu)建了大約2^31不必要的Long實(shí)例(基本上每次long i加到Long sum上都要?jiǎng)?chuàng)建一個(gè))。將sum從Long聲明為long之后,在我機(jī)器上運(yùn)行時(shí)間從43秒降到了6.8秒。結(jié)論很明顯:使用基本類型優(yōu)先于包裝類,當(dāng)心無(wú)意的自動(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)建對(duì)象是昂貴的,應(yīng)該避免創(chuàng)建對(duì)象。恰恰相反,創(chuàng)建和回收構(gòu)造函數(shù)做很少顯式工作的小對(duì)象是非常廉價(jià)的,尤其是在現(xiàn)代的JVM實(shí)現(xiàn)上。創(chuàng)建額外的對(duì)象來(lái)增強(qiáng)程序的清晰性,簡(jiǎn)潔性,或能力通常是一件好事。
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.
相反的,通過(guò)維護(hù)你自己的對(duì)象池來(lái)避免創(chuàng)建對(duì)象是一個(gè)壞主意,除非對(duì)象池中的對(duì)象是極度重量級(jí)的。真正證明對(duì)象池的對(duì)象經(jīng)典例子是數(shù)據(jù)庫(kù)連接。建立連接的代價(jià)是非常大的,因此復(fù)用這些對(duì)象是很有意義的。數(shù)據(jù)庫(kù)許可可能也限制你使用固定數(shù)目的連接。但是,通常來(lái)說(shuō)維護(hù)你自己的對(duì)象池會(huì)使你的代碼很亂,增加內(nèi)存占用,而且損害性能?,F(xiàn)代JVM實(shí)現(xiàn)有高度優(yōu)化的垃圾回收機(jī)制,維護(hù)輕量級(jí)對(duì)象很容易比對(duì)象池做的更好。
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.
與本條目對(duì)應(yīng)的是Item 39 保護(hù)性拷貝。Item 5 聲稱,『不要?jiǎng)?chuàng)建一個(gè)新的對(duì)象,當(dāng)你應(yīng)該復(fù)用一個(gè)現(xiàn)有的對(duì)象時(shí)』,而Item 39 聲稱,『不要重用一個(gè)現(xiàn)有的對(duì)象,當(dāng)你應(yīng)該創(chuàng)建一個(gè)新的對(duì)象時(shí)』。注意,當(dāng)保護(hù)性拷貝時(shí)復(fù)用一個(gè)對(duì)象的代價(jià)要遠(yuǎn)大于創(chuàng)建一個(gè)不必要的重復(fù)對(duì)象的代價(jià)。當(dāng)需要時(shí)沒有創(chuàng)建一個(gè)保護(hù)性拷貝可能導(dǎo)致潛在的錯(cuò)誤和安全漏洞;創(chuàng)建不必要的對(duì)象只會(huì)影響程序風(fēng)格及性能。