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).

每次需要一个对象时,与创建一个新的功能相同的对象相比,复用一个对象经常是合适的。复用更快更流行。如果一个对象是不变的,那它总是可以复用。(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.

这条语句每次执行时都会创建一个新的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类的改进版本只在初始化时创建CalendarTimeZoneDate实例一次,而不是每次调用isBabyBoomer方法都创建它们。如果isBabyBoomer方法被频繁调用的话,这样做在性能上会有很大提升。在我的机器上,最初的版本一千万次调用要花费32,000毫秒,而改进版本只花了130毫秒,比最初版本快了大约250倍。不仅性能改善了,代码也更清晰了。将boomStartboomEnd从局部变量变为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_STARTBOOM_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上都要创建一个)。将sumLong声明为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 声称,『不要重用一个现有的对象,当你应该创建一个新的对象时』。注意,当保护性拷贝时复用一个对象的代价要远大于创建一个不必要的重复对象的代价。当需要时没有创建一个保护性拷贝可能导致潜在的错误和安全漏洞;创建不必要的对象只会影响程序风格及性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值