文章作者: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).
每次需要一个对象时,与创建一个新的功能相同的对象相比,复用一个对象经常是合适的。复用更快更流行。如果一个对象是不变的,那它总是可以复用。(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.
这条语句每次执行时都会创建一个新的String
实例,这些对象的创建都是没必要的。String
构造函数的参数"stringette"
本身就是一个String
实例,在功能上与构造函数创建的所有对象都是等价的。如果这种用法出现在一个循环或一个频繁调用的方法中,会创建出成千上万的不必要的String
实例。
The improved version is simply the following:
改进版本如下:
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
实例,而不是每次执行时创建一个新实例。此外,它保证了运行在虚拟中包含同样字符串的任何其它代码都可以复用这个对象[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.
对于提供了构造函数和静态工厂方法的不变类,使用静态工厂方法(Item 1)优先于构造函数常常可以让你避免创建不必要的对象。例如,静态工厂方法Boolean.valueOf
(String)总是优先于构造函数Boolean
(String)。每次调用构造函数都会创建一个新的对象,而静态工厂方法从来不要求这样做,在实践中也不会这样做。
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:
除了复用不可变对象之外,如果你知道可变对象不会被修改,你也可以复用可变对象。下面是一个比较微妙,更为常见反面例子。它包含可变的Date
对象,这些Date
对象一旦计算出来就不再修改。这个类对人进行了建模,其中有一个isBabyBoomer
方法用来区分这个人是否是一个“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:
每次调用时,isBabyBoomer
方法都会创建一个Calendar
实例,一个TimeZone
实例和两个Date
实例,这是不必要的。下面的版本用静态初始化避免了这种低效率的问题:
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
类的改进版本只在初始化时创建Calendar
,TimeZone
和Date
实例一次,而不是每次调用isBabyBoomer
方法都创建它们。如果isBabyBoomer
方法被频繁调用的话,这样做在性能上会有很大提升。在我的机器上,最初的版本一千万次调用要花费32,000毫秒,而改进版本只花了130毫秒,比最初版本快了大约250倍。不仅性能改善了,代码也更清晰了。将boomStart
和boomEnd
从局部变量变为static final
字段,很明显是将它们看作常量,代码也更容易理解。从整体收益来看,这种优化的节约并不总是这么戏剧性的,因为Calendar
实例创建的代价是非常昂贵的。
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
类的改进版本,但从不调用它的isBabyBoomer
方法,BOOM_START
和BOOM_END
字段的初始化就是不必要的。可以通过延迟初始化(当需要时再初始化)这些字段(Item 71)来消除这些不必要的初始化,当第一次调用isBabyBoomer
方法时再进行初始化,但不推荐这样做。延迟初始化是常有的事,它的实现是非常复杂的,除了我们已有的性能提升之外,延迟初始化不可能引起明显的性能提升(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.
在本条目前面的例子中,很明显问题中的对象可以复用,因为它们在初始化之后没有被修改。但在其它的情况下它就不那么明显了。考虑一个适配器的情况[Gamma95, p. 139],也称之为视图。适配器是代理支持对象的对象,为支持对象提供了一个可替代的接口。由于适配器除了它的支持对象之外没有别的状态,因此没必要创建多个给定对象的适配器实例。
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。乍一看,好像每一次调用keySet
方法都会创建一个新的Set
实例,但在一个给定的Map
对象上每次调用keySet
方法可能返回的都是同一个Set
实例。虽然返回的Set
实例通常都是可变的,但所有的返回对象在功能上是等价的:当一个返回对象改变时,其它的都要改变,因为它们都由同一个Map
实例支持。虽然创建多个keySet
视图对象的实例是无害的,但它是没必要的。
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中有一种新的方式来创建不必要对象。它被称为自动装箱,它允许程序员混合使用基本类型和它们的包装类型,JDK会在需要时自动装箱和拆箱,自动装箱虽然模糊但不能去除基本类型和包装类之间的区别。它们在语义上有稍微的不同,但不是轻微的性能差异(Item 49)。看一下下面的程序,计算所有正数int
值的总和。为了计算这个,程序必须使用long
类型,因为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.
这个程序算出了正确答案,但由于一个字符的错误,它运行的更慢一些。变量sum
声明为Long
而不是long
,这意味着程序构建了大约2^31不必要的Long
实例(基本上每次long i
加到Long sum
上都要创建一个)。将sum
从Long
声明为long
之后,在我机器上运行时间从43秒降到了6.8秒。结论很明显:使用基本类型优先于包装类,当心无意的自动装箱。
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.
不该将本条目误解成暗示创建对象是昂贵的,应该避免创建对象。恰恰相反,创建和回收构造函数做很少显式工作的小对象是非常廉价的,尤其是在现代的JVM实现上。创建额外的对象来增强程序的清晰性,简洁性,或能力通常是一件好事。
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.
相反的,通过维护你自己的对象池来避免创建对象是一个坏主意,除非对象池中的对象是极度重量级的。真正证明对象池的对象经典例子是数据库连接。建立连接的代价是非常大的,因此复用这些对象是很有意义的。数据库许可可能也限制你使用固定数目的连接。但是,通常来说维护你自己的对象池会使你的代码很乱,增加内存占用,而且损害性能。现代JVM实现有高度优化的垃圾回收机制,维护轻量级对象很容易比对象池做的更好。
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.
与本条目对应的是Item 39 保护性拷贝。Item 5 声称,『不要创建一个新的对象,当你应该复用一个现有的对象时』,而Item 39 声称,『不要重用一个现有的对象,当你应该创建一个新的对象时』。注意,当保护性拷贝时复用一个对象的代价要远大于创建一个不必要的重复对象的代价。当需要时没有创建一个保护性拷贝可能导致潜在的错误和安全漏洞;创建不必要的对象只会影响程序风格及性能。