【Effective Java 06】避免创建不必要的对象

一般来说,最好能重用单个对象,而不是在每次需要的时候就创建一个相同功能的新对象。

如果对象始终是不可变的,则它始终可以被重用。

1. 对于某些不可变对象

String 类型的变量

String s = new String("Hello"); // DON'T DO THIS

上面的语句每次执行的时候都会创建一个新的 String 实例, 但这些创建对象的动作是不必要的。

传递给 String 构造器的参数本身就是一个 String 实例。

如果这种用法被放到一个循环中,或者一个被频繁调用的方法,就会创建出成千上万的不必要的 String 实例。

String s = "Hello"; // DO THIS

上面修改后的代码只是用了一个 String 实例,而不是每次创建一个新的实例。而且它可以保证,对于所有在同一台虚拟机中运行的代码,只要它们包含相同的字符串字面常量,该对象就会被重用 [JLS, 3.10.5]

2. 尽可能使用静态工厂方法

对于提供了静态工厂方法的类对象,应尽可能使用静态工厂方法,以避免创建不必要的对象。

// BEFORE JDK 8
Boolean flag1 = new Boolean("true"); // DON'T DO THIS
Boolean flag2 = Boolean.valueOf("true"); // DO THIS

3. 对于创建开销大的对象

对于一些创建开销大,但由可复用的对象如正则表达式功能中的 Pattern 对象。

public class RomanNumerals {
    private static final Pattern ROMAN_PATTERN = Pattern.compile(
            "^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"
    );
	
    // DO THIS
    static boolean isRomanNumeralGood(String s) {
        return ROMAN_PATTERN.matcher(s).matches();
    }

    // DON'T DO THIS, special in loop
    static boolean isRomanNumeralBad(String s) {
        return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
    }
}

4. 避免在适配器中创建多余对象

如果一个对象是不变的,那么它显然能够被安全地重用,但其他有些情形则并不总是这么明显。考虑适配器(adapter)的情形,有时也叫视图(view)。适配器是指这样一个对象:它把功能委托给一个后备对象,从而为后备对象提供一个可以替代的接口。由于适配器除了后备对象之外,没有其他的状态信息,所以针对某一个给定对象的特定适配器而言,它不需要创建多个适配器实例。

例如,Map接口的keySet方法返回该Map对象的Set适配器,其中包含该Map中所有的键。乍看之下,好像每次调用 keySet 都应该创建一个新的 Set 实例,但是,对于一个给定的 Map 对象,实际上每次调用 keySet 都返回同样的 Set 实例。虽然被返回的 Set 实例一般是可改变的,但是所有返回的对象在功能上是等同的;当其中一个返回对象发生变化的时候,所有其他的返回对象也要发生变化,因为它们都是由同一个 Map 实例支撑的。

5. 优先使用基本类型而不是装箱基本类型,避免无意识的自动装箱

自动装箱比不自动装箱慢很多,每次自动装箱都要创建新对象,尽量避免。

// DON'T DO THIS, Spend Time: 8.54 s
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
    sum += i; // 自动装箱, 每次都会将 i 自动装箱, 导致速度减慢
}

// DO THIS, Spend Time: 1.64 s
long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
    sum += i; // 无自动装箱
}

posted on 2022-03-21 16:56  Silgm  阅读(62)  评论(0)    收藏  举报

导航