一般来说,最好能重用对象而不是在每次需要的时候就创建一个相同功能的新对象。重用方式既快速,又流行。如果对象时不可变的(immutable),它就始终可以被重用。
作为一个极端的反面例子,考虑下面的语句:
String s = new String("stringette"); // DON'T DO THIS!
该语句每次被执行的时候都创建一个新的String实例,但是这些创建对象的动作全都是不必要的。传递给String构造器的参数("stringete")本身就是一个String实例,功能方面等同于构造器创建的所有对象。如果这种用法是在一个循环中,或者是在一个被频繁调用的方法中,就会创建出成千上万不必要的String实例。
改进后的版本如下所示:
String s = "stringette";
这个版本只用了一个String实例,而不是每次执行的时候都创建一个新的实例。而且,它可以保证,对于所有在同一台虚拟机中运行的代码,只要他们包含相同的字符串字面常量,该对象就会被重用。
对于同时提供了静态工厂方法和构造器的不可变类,通常可以使用静态工厂方法而不是构造器,以避免创建不必要的对象。例如,静态工厂方法Boolean.valueOf(String)几乎总是优先于构造器Boolean(String)。构造器在每次被调用的时候都会创建一个新的对象,而静态工厂方法则从来不要求这样做,实际上也不会这样做。
除了重用不可变的对象外,也可以重用哪些已知不会被修改的可变对象。
注意,在提倡使用保护性拷贝的时候,因重用对象而付出的代价要远远大于因创建重复对象而付出的代价。必要时如果没能实施保护性拷贝,将会导致潜在的错误和安全漏洞,而不必要的创建对象则只会影响程序的风格和性能。
示例代码:
反例
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(TimZone.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;
}
isBabyBoomer每次被调用的时候,都会新创建一个Calendar、一个TimeZone和两个Date实例,这是不必要的。下面的版本用一个静态的初始化器(initializer),避免了这种效率低下的情况:
class Person {
private final Date birthDate;
// Other fields,methods,and constructor comitted
/ **
* 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();
gtmCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_END = gmtCal.getTime();
}
public boolean isBabyBoomer() {
return birthDate.compareTo(BOOM_START) >= 0 && birthDate.comparte(Boom_END) < 0;
}
}
改进后的Person类只在初始化创建的时候创建Calendar、TimeZone和Date实例一次,而不是在每次调用isBabyBoomer的时候都创建这些实例。如果isBabyBoomer方法被频繁的调用,这种方法将会显著提高性能。在我的机器上,每调用一千万次,原来的版本需要32 000ms,而改进后的版本只需要130ms,大约快了250倍。除了提高性能外,代码的含义也更加清晰了。把boomStart和boomEnd从局部变量改为final静态域,这些日期显然是被作为常量对待,从而使得代码更易于理解。但是,这种优化带来的效果并不总是那么明显,因为Calendar实例的创建代价特别昂贵。