文章作者: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 String
constructor ("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
冈止,TimeZone
和Date
實(shí)例一次狂票,而不是每次調(diào)用isBabyBoomer
方法都創(chuàng)建它們。如果isBabyBoomer
方法被頻繁調(diào)用的話熙暴,這樣做在性能上會有很大提升闺属。在我的機(jī)器上,最初的版本一千萬次調(diào)用要花費(fèi)32,000毫秒周霉,而改進(jìn)版本只花了130毫秒掂器,比最初版本快了大約250倍。不僅性能改善了俱箱,代碼也更清晰了国瓮。將boomStart
和boomEnd
從局部變量變?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_START
和BOOM_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)建一個)。將sum
從Long
聲明為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)格及性能。