第3条.用私有构造器或者枚举类型强化Singleton属性
Singleton是指仅仅被实例化一次的类。
实现Singleton的两种常见方法:
1.公有静态成员是final域
package com.example.ownlearn;
public class Elvis {
private static int count = 0;
public static final Elvis INSTANCE = new Elvis();
private Elvis(){count++;}
public static int getCount() {
return count;
}
}
2.公有成员是静态工厂方法:
package com.example.ownlearn;
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis(){count++;}
public static int getCount() {
return count;
}
public static Elvis getInstance(){return INSTANCE;}
}
对于静态方法 Elvis.getInstance 的所有调用,都会返回同一个对象引用。
但是这两种方法都有一个相同的问题,那就是我们依然可以通过反射的方法,调用到构造函数,创建新实例:
package com.example.ownlearn;
import java.lang.reflect.*;
import static com.example.ownlearn.NyPizza.Size.SMALL;
public class Test {
public void get() {
Elvis elvis = Elvis.INSTANCE;
int count = elvis.getCount();
Constructor[] constructors = elvis.getClass().getDeclaredConstructors();
AccessibleObject.setAccessible(constructors, true);
Constructor<Elvis> ce = constructors[0];
try {
elvis = ce.newInstance();
count = elvis.getCount();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
为了将上述方法实现的Singleton类变成可序列化的,仅仅在声明中加上implements Serializable是不够的。为了维护和并保证Singleton,必须声明所有实例域都是瞬时的,并提供一个readResolve方法,否则每次反序列化一个序列化的实例时,都会创建一个新的实例。
private Object readSolve(){
return INSTANCE;
}
3.声明一个包含单个元素的枚举类型:
public enum Elvis{
INSTANCE;
}
单元素的枚举类型经常成为实现Singleton的最佳方法。
第4条.通过私有构造器强化不可实例化的能力
当类不包含显示的构造器时,编译器才会生成缺省的构造器,因此只要让这个类包含一个私有的构造器,他就不能被实例化:
public class UtilityClass{
private UtilityClass(){
throw new AssertionError();
}
}
第5条.优先考虑依赖注入来引用资源
满足该需求的最简单的模式是,当创建一个新的实例时,就将该资源传到构造器中。这是依赖注入的一种形式。
public class SpellChecker{
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary){
this.dictionary = Objects.requireNonNull(dictionary);
}
}
第6条.避免创建不必要的对象
一般来说,最好能重用单个对象,而不是在每次需要的时候就创建一个相同功能的新对象。
对于同时提供了静态工厂方法和构造器的不可变类,通常优先使用静态工厂方法而不是构造器,以免创建不必要的对象。
比如我们想要编写一个方法,用它确定一个字符串是否为一个有效的罗马数字。
static boolean isRomanNumeral(String s){
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
+"(x[CL]|L?X{0,3})(I[XV]|V?I{0,3})$)");
}
但是它依赖String.matches方法。该方法内部会创建一个Pattern实例,却只用了一次,之后就会进行垃圾回收。创建Pattern实例的成本很高。为了提升性能,应该显示将正则表达式编译成一个Pattern实例,让它成为类初始化的一部分,并将它缓存起来。
public class RomanNumerals{
private static final Pattern ROMAN = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})"
+"(x[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
static boolean isRomanNumeral(String s){
return ROMAN.matcher(s).matches();
}
}
另一种创建多余对象的方法,称作自动装箱。
要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。
通常通过维护自己的对象池来避免创建对象并不是一种好的做法,除非池中的对象是非常重量级的,比如数据库连接池。
注意:在提倡使用保护性拷贝的时候,因重用对象而付出的代价要远远大于因重复对象而付出的代价。必要时如果没能实施保护性拷贝,将会导致潜在的Bug和安全漏洞;而不必要地创建对象只会影响程序的风格和性能。