此条在中文版第二版中被译为了“避免创建不必要的对象”,用此更加严谨了。
此条认为重复使用同一对象,比每次需要时都创建一个功能上相等价的新对象更好。如果对象是非可变的(见【第13条】),那么他总是可以被重用的。
一例子是:
String s1 = "Hello World !";
........ // 其他一些列代码
String s2= "Hello World !";
虽然看起来s2是新创建的一个新对象,但是由于String是非可变的,所以 String s2= "Hello World !";
等同于 String s2 = s1; 实际内存中并没有新开辟一块空间,而是将s2的地址指针指向s1的空间地址。
但是如果写成 String s2 = new String(s1); 则系统会为s2新开辟一块内存空间,这是我们所不希望看到的。
进而举个更为极端的反例: String s = new String("Hello !");
由于实参"Hello !"本身就是一个String的实例,这时候实际上已经创建了一个String的实例了,再用之作为new String()的参数,就再次创建了一个String实例给s,于是内存的消耗就Double了!
对于提供了静态工厂方法(见【第1条】)和构造函数的非可变类,你通常可以利用静态工厂方法而不是构造函数,以避免重复创建对象。例如,Boolean.valueOf(String)几乎总是优先于构造函数Boolean(String)的。因为构造函数每次被调用都会创建一个新的对象,而静态工厂方法从来不要求这样做。
再有就是那些已知不会被修改的对象,它们一旦被计算出来就不再变化,可以随时使用,而不必每次使用前再次计算。例如,有一个SystemInfo类,里面有保存和取得系统信息的域和方法。为了简单我们认为只有一个用于获取和保持CPUID的方法和域。如果这个类写的不好的话,可能在每次调用 getCPUID()时,都要重新创建SystemInfo中的cpuId,重新获取,甚至重新创建一个SystemInfo类;而好的代码,是在第一次调用时获取,并保持起来,之后再次调用时只是返回就可以了。
以下是我的一段真实代码中的截取:
public class SystemInfo {
private static final SystemInfo INSTANCE = new SystemInfo();
private static List<String> NULL_STRING_LIST; // 应该是一个final常量,但由于这里的执行顺序在构造函数之后,所以要在被构造函数调用的方法中使用这个常量就不可以了,所以才这样写
// 是用List是因为CPU可能不止一个
private List<String> cpuIds;
/**
* 这是一个私有的构造函数 目的是禁止此类被外部实例化,因为这是一个单例模式
*/
private SystemInfo() {
NULL_STRING_LIST = new ArrayList<String>();
this.cpuIds = getCPUIDs();
}
public static SystemInfo getInstance() {
return INSTANCE;
}
public List<String> getCPUID() {
return this.cpuIds;
}
private List<String> getCPUIDs() {
try {
return Arrays.asList(...);
} catch (Exception e) {
return NULL_STRING_LIST; // 见【第27条】返回零长度的数组而不是null,推想一下ArrayList也是同理
}
}
}
使用方法: String myCpu = SystemInfo.getInstance().getCPUID();
这个例子综合了【前4条】的知识,并且还包括了【第27条】,是我能想出的比较好的例子了。
【第1条】它提供了静态工厂方法,用于代替公有构造函数
【第2条】它使用私有构造函数来强化它是个单例模式
【第3条】它使用私有构造函数来强化不可实例化的能力(但与第3条不完全相同,它允许内部实例化一次)
【第27条】异常时返回零长度的数组而不是null,避免使用者需要过多的保护性代码
进而这段代码的一个细微变形版本如下,不再是单例模式,而是不可实例化的工具类了。(是【第2条】的进一步,完全满足【第3条】)
public class SystemInfo {
private static final SystemInfo INSTANCE = new SystemInfo();
private static final List<String> NULL_STRING_LIST = new ArrayList<String>();
// 是用List是因为CPU可能不止一个
private static List<String> cpuIds;
/**
* 这是一个空的私有的构造函数 目的是禁止此类被实例化,因为这是一个工具类 */
private SystemInfo() {
}
static{
cpuIds = getCPUIDs();
}
public static SystemInfo getInstance() {
return INSTANCE;
}
public List<String> getCPUID() {
return cpuIds;
}
private static List<String> getCPUIDs() {
try {
return Arrays.asList(...);
} catch (Exception e) {
return NULL_STRING_LIST; // 见【第27条】返回零长度的数组而不是null,推想一下ArrayList也是同理
}
}
}
以上两段哪种写法更好呢?可能仁者见仁吧,实际中我使用的是后者,而且是把这个类实现了一个接口,因为考虑到可能会有不同版本的“取得系统信息”的方法(Win32的、Linux的、JNI的...)。
而且当使用Spring的注入后,getInstance()方法 和 INSTANCE 常量也不再是必须的了(Spring的注入不在现在的讨论之内)
【Effective Java 学习笔记】系列连载专题请见:
http://tonylian.iteye.com/categories/64208