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 17).
重用單個對象通常是合適的,不必每次需要時都創(chuàng)建一個新的功能等效對象。重用可以更快、更時尚。如果對象是不可變的,那么它總是可以被重用的(Item-17)。
As an extreme example of what not to do, consider this statement:
作為一個不該做的極端例子,請考慮下面的語句:
String s = new String("bikini"); // 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 ("bikini") 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 實例,而這些對象創(chuàng)建都不是必需的。String 構(gòu)造函數(shù)的參數(shù)("bikini")本身就是一個 String 實例,在功能上與構(gòu)造函數(shù)創(chuàng)建的所有對象相同。如果這種用法發(fā)生在循環(huán)或頻繁調(diào)用的方法中,則不必要創(chuàng)建數(shù)百萬個 String 實例。
The improved version is simply the following:
改進(jìn)后的版本如下:
String s = "bikini";
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 實例,而不是每次執(zhí)行時都創(chuàng)建一個新的實例。此外,可以保證在同一虛擬機中運行的其他代碼都可以重用該對象,只要恰好包含相同的字符串字面量 [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 factory method Boolean.valueOf(String) is preferable to the constructor Boolean(String), which was deprecated in Java 9. The constructor must create a new object each time it’s called, while the factory method is never required to do so and won’t in practice. In addition to reusing immutable objects, you can also reuse mutable objects if you know they won’t be modified.
你通??梢酝ㄟ^使用靜態(tài)工廠方法(Item-1)來避免創(chuàng)建不必要的對象,而不是在提供這兩種方法的不可變類上使用構(gòu)造函數(shù)。例如,工廠方法 Boolean.valueOf(String) 比構(gòu)造函數(shù) Boolean(String) 更可取,后者在 Java 9 中被棄用了 。構(gòu)造函數(shù)每次調(diào)用時都必須創(chuàng)建一個新對象,而工廠方法從來不需要這樣做,在實際應(yīng)用中也不會這樣做。除了重用不可變對象之外,如果知道可變對象不會被修改,也可以重用它們。
Some object creations are much more expensive than others. If you’re going to need such an “expensive object” repeatedly, it may be advisable(adj.明智的,適當(dāng)?shù)模?to cache it for reuse. Unfortunately, it’s not always obvious when you’re creating such an object. Suppose you want to write a method to determine(v.下決心;vt.確定) whether a string is a valid Roman numeral. Here’s the easiest way to do this using a regular expression:
有些對象的創(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})$");
}
The problem with this implementation is that it relies on the String.matches method. While String.matches is the easiest way to check if a string matches a regular expression, it’s not suitable for repeated use in performance-critical situations. The problem is that it internally creates a Pattern instance for the regular expression and uses it only once, after which it becomes eligible for garbage collection. Creating a Pattern instance is expensive because it requires compiling the regular expression into a finite state machine.
這個實現(xiàn)的問題是它依賴于 String.matches 方法。雖然 String.matches 是檢查字符串是否與正則表達(dá)式匹配的最簡單方法,但它不適合在性能關(guān)鍵的情況下重復(fù)使用。 問題是,它在內(nèi)部為正則表達(dá)式創(chuàng)建了一個模式實例,并且只使用一次,之后就可以進(jìn)行垃圾收集了。創(chuàng)建一個模式實例是很昂貴的因為它需要將正則表達(dá)式編譯成有限的狀態(tài)機制。
To improve the performance, explicitly compile the regular expression into a Pattern instance (which is immutable) as part of class initialization, cache it,and reuse the same instance for every invocation of the isRomanNumeral method:
為了提高性能,將正則表達(dá)式顯式編譯為模式實例(它是不可變的),作為類初始化的一部分,緩存它,并在每次調(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();
}
}
The improved version of isRomanNumeral provides significant performance gains if invoked frequently. On my machine, the original version takes 1.1 μs on an 8-character input string, while the improved version takes 0.17 μs, which is 6.5 times faster. Not only is the performance improved, but arguably, so is clarity. Making a static final field for the otherwise invisible Pattern instance allows us to give it a name, which is far more readable than the regular expression itself.
如果頻繁調(diào)用 isRomanNumeral,改進(jìn)版本將提供顯著的性能提升。在我的機器上,原始版本輸入 8 字符的字符串花費 1.1μs,而改進(jìn)的版本需要 0.17μs,快 6.5 倍。不僅性能得到了改善,清晰度也得到了提高。為不可見的模式實例創(chuàng)建一個靜態(tài)終態(tài)字段允許我們?yōu)樗@比正則表達(dá)式本身更容易閱讀。
If the class containing the improved version of the isRomanNumeral method is initialized but the method is never invoked, the field ROMAN will be initialized needlessly. It would be possible to eliminate the initialization by lazily initializing the field (Item 83) the first time the isRomanNumeral method is invoked, but this is not recommended. As is often the case with lazy initialization, it would complicate the implementation with no measurable performance improvement (Item 67).
如果加載包含改進(jìn)版 isRomanNumeral 方法的類時,該方法從未被調(diào)用過,那么初始化字段 ROMAN 是不必要的。因此,可以用延遲初始化字段(Item-83)的方式在第一次調(diào)用 isRomanNumeral 方法時才初始化字段,而不是在類加載時初始化,但不建議這樣做。通常情況下,延遲初始化會使實現(xiàn)復(fù)雜化,而沒有明顯的性能改善(Item-67)。
譯注:類加載通常指的是類的生命周期中加載、連接、初始化三個階段。當(dāng)方法沒有在類加載過程中被使用時,可以不初始化與之相關(guān)的字段
When an object is immutable, it is obvious it can be reused safely, but there are other situations where it is far less obvious, even counterintuitive. Consider the case of adapters [Gamma95], also known as views. An adapter is an object that delegates to a backing object, providing an alternative interface. 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.
當(dāng)一個對象是不可變的,很明顯,它可以安全地重用,但在其他情況下,它遠(yuǎn)不那么明顯,甚至違反直覺??紤]適配器的情況 [Gamma95],也稱為視圖。適配器是委托給支持對象的對象,提供了一個替代接口。因為適配器的狀態(tài)不超過其支持對象的狀態(tài),所以不需要為給定對象創(chuàng)建一個給定適配器的多個實例。
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(adj.易變的), 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 largely harmless to create multiple instances of the keySet view object, it is unnecessary and has no benefits.
例如,Map 接口的 keySet 方法返回 Map 對象的 Set 視圖,其中包含 Map 中的所有鍵。天真的是,對 keySet 的每次調(diào)用都必須創(chuàng)建一個新的 Set 實例,但是對給定 Map 對象上的 keySet 的每次調(diào)用都可能返回相同的 Set 實例。雖然返回的 Set 實例通常是可變的,但所有返回的對象在功能上都是相同的:當(dāng)返回的對象之一發(fā)生更改時,所有其他對象也會發(fā)生更改,因為它們都由相同的 Map 實例支持。雖然創(chuàng)建 keySet 視圖對象的多個實例基本上是無害的,但這是不必要的,也沒有好處。
Another way to create unnecessary objects is autoboxing, which 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 61). Consider the following method, 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:
另一種創(chuàng)建不必要對象的方法是自動裝箱,它允許程序員混合原始類型和包裝類型,根據(jù)需要自動裝箱和拆箱。自動裝箱模糊了原始類型和包裝類型之間的區(qū)別, 兩者有細(xì)微的語義差別和不明顯的性能差別(Item-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;
}
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 231 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 6.3 seconds to 0.59 seconds on my machine. The lesson is clear: prefer primitives to boxed primitives, and watch out for unintentional autoboxing.
這個程序得到了正確的答案,但是由于一個字符的印刷錯誤,它的速度比實際要慢得多。變量 sum 被聲明為 Long 而不是 long,這意味著程序?qū)?gòu)造大約 231 個不必要的 Long 實例(大約每次將 Long i 添加到 Long sum 時都有一個實例)。將 sum 的聲明從 Long 更改為 long,機器上的運行時間將從 6.3 秒減少到 0.59 秒。教訓(xùn)很清楚:基本數(shù)據(jù)類型優(yōu)于包裝類,還應(yīng)提防意外的自動裝箱。
This item should not be misconstrued(vt.誤解,曲解) 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.
這個項目不應(yīng)該被曲解為是在暗示創(chuàng)建對象是昂貴的,應(yīng)該避免。相反,創(chuàng)建和回收這些小對象的構(gòu)造函數(shù)成本是很低廉的,尤其是在現(xiàn)代 JVM 實現(xiàn)上。創(chuà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. 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)建對象不是一個好主意,除非池中的對象非常重量級。證明對象池是合理的對象的典型例子是數(shù)據(jù)庫連接。建立連接的成本非常高,因此重用這些對象是有意義的。然而,一般來說,維護(hù)自己的對象池會使代碼混亂,增加內(nèi)存占用,并損害性能?,F(xiàn)代 JVM 實現(xiàn)具有高度優(yōu)化的垃圾收集器,在輕量級對象上很容易勝過這樣的對象池。
The counterpoint to this item is Item 50 on defensive copying. The present item says, “Don’t create a new object when you should reuse an existing one,”while Item 50 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-50(防御性復(fù)制)。當(dāng)前項的描述是:「在應(yīng)該重用現(xiàn)有對象時不要創(chuàng)建新對象」,而 Item 50 的描述則是:「在應(yīng)該創(chuàng)建新對象時不要重用現(xiàn)有對象」。請注意,當(dāng)需要進(jìn)行防御性復(fù)制時,重用對象所受到的懲罰遠(yuǎn)遠(yuǎn)大于不必要地創(chuàng)建重復(fù)對象所受到的懲罰。在需要時不制作防御性副本可能導(dǎo)致潛在的 bug 和安全漏洞;不必要地創(chuàng)建對象只會影響樣式和性能。
Back to contents of the chapter(返回章節(jié)目錄)
- Previous Item(上一條目):Item 5: Prefer dependency injection to hardwiring resources(依賴注入優(yōu)于硬連接資源)
- Next Item(下一條目):Item 7: Eliminate obsolete object references(排除過時的對象引用)