使类成为Singleton会使得它的客户端代码测试变得困难,因为无法给它替换模拟实现,,除非它实现了一个充当其类型的接口。当使用DI框架的时候,一定给每一个注册的Component抽出来一个借口,方便客户端进行测试。
Singleton暴露getInstance方法的好处
书中提到的Singleton最好不要直接把INSTANCE对象暴露给外面,而是封装一个getInstance方法:
public class Elvis {
privaye static final Elvis INSTANCE = new Elvis();
private Elvis() { }
public static Elvis getInstance() {
return INSTANCE;
}
public void leaveTheBuilding() {
}
}
这样做有3袋内好处:
-
可以在不修改API的情况下改变是否该类为Singleton。具体的意思就是,可以在不修改getIntance的参数和返回值的情况下,通过修改实现控制是Application唯一还是线程唯一(ThreadLocal)
-
可以用来使用泛型Singleton工厂,这个内容不是很容易理解,书中提到了一个例子,
Collections.emptySet
方法:public static final <T> Set<T> emptySet() { return (Set<T>) EMPTY_SET; } public static final Set EMPTY_SET = new EmptySet<>();
使用这个方法会发生警告,因为Set并不一定是Set<T>,但是对于空Set来说是无所谓的
-
可以通过方法引用作为提供者
比如上面的
Elivis::getInstance
就是Supplier\<Elivis\>
序列化问题
序列化可以打破单例
public class SingletonClass implements Serializable {
private static final SingletonClass INSTANCE = new SingletonClass();
private SingletonClass() {
}
public static SingletonClass getInstance() {
return INSTANCE;
}
}
public class Test {
public static void main(String[] args) throws Exception {
writeObject();
Object obj = readObject();
System.out.println(obj instanceof SingletonClass);
System.out.println(SingletonClass.getInstance() == obj);
}
private static Object readObject() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test_singleton"));
Object obj = ois.readObject();
ois.close();
return obj;
}
private static void writeObject() throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test_singleton"));
oos.writeObject(SingletonClass.getInstance());
oos.flush();
oos.close();
}
}
上面那段代码是讲SingletonClass的instance序列化后再反序列化,程序的执行结果是:

可以看到,用了序列化之后就不能保证单例了。解决这个问题的方式就是给SingletonClass加一个readResolve方法:
public class SingletonClass implements Serializable {
private static final SingletonClass INSTANCE = new SingletonClass();
private SingletonClass() {
}
public static SingletonClass getInstance() {
return INSTANCE;
}
@Serial
private Object readResolve() {
return INSTANCE;
}
}
这个时候再执行程序,结果就是一样的了:

单例的最佳方法
单例模式的最佳方法是使用枚举:
public enum Elvis {
INSTANCE;
public void leaveTheBuilding(){}
}
使用枚举的特性避免了线程安全和序列化问题
网友评论