先看一个单例:
public class Singleton{
private final static Singleton INSTANCE = new Singleton();
private Singleton(){};
public static Singleton getInstance(){return INSTANCE;}
}
我们用序列化来打破单例
public class Singleton implements Serializable{
private final static Singleton INSTANCE = new Singleton();
private Singleton(){};
public static Singleton getInstance(){return INSTANCE;}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
Singleton s1 = Singleton.getInstance();
File objectF = new File("/object");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(objectF));
out.writeObject(s1);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream(objectF));
Singleton s2 = (Singleton) in.readObject();
in.close();
System.out.println("是单例么?" + (s1 == s2));
}
}
将会打印:
是单例么?false。
可见我们可以这样破坏其单例属性。要保持应该怎么办呢?需要增加readResolve方法,Java反序列化的时候会用这个方法的返回值直接代替序列化得到的对象
public class Singleton implements Serializable{
private final static Singleton INSTANCE = new Singleton();
private Singleton(){};
public static Singleton getInstance(){return INSTANCE;}
private Object readResolve() {
return INSTANCE;
}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
Singleton s1 = Singleton.getInstance();
File objectF = new File("/object");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(objectF));
out.writeObject(s1);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream(objectF));
Singleton s2 = (Singleton) in.readObject();
in.close();
System.out.println("是单例么?" + (s1 == s2));
}
}
打印:
是单例么?true
我们再通过反射来打破其的单例性:
public class Singleton{
private final static Singleton INSTANCE = new Singleton();
private Singleton(){};
public static Singleton getInstance(){return INSTANCE;}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
Singleton s1 = Singleton.getInstance();
Constructor<Singleton> c = Singleton.class.getDeclaredConstructor();
c.setAccessible(true);
Singleton s2 = c.newInstance();
System.out.println("是单例么?" + (s1 == s2));
}
}
将会打印:
是单例么?false。
说明使用反射调用私有构造器也是可以破坏单例的,解决的办法是如下:public class Singleton{
private final static Singleton INSTANCE = new Singleton();
private Singleton(){
if(++COUNT > 1){
throw new RuntimeException("can not be construt more than once");
}
};
private static int COUNT = 0;
public static Singleton getInstance(){
return INSTANCE;
}
}
这样当使用反射调用的时候,就会抛出异常。
再用clone来破坏单例性
public class Singleton implements Cloneable{
private final static Singleton INSTANCE = new Singleton();
public static Singleton getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = (Singleton) s1.clone();
System.out.println("是单例么?" + (s1 == s2));
}
}
这样也会发现不是单例了,办法是重新clone方法。
public class Singleton implements Cloneable{
private final static Singleton INSTANCE = new Singleton();
public static Singleton getInstance(){
return INSTANCE;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return INSTANCE;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = (Singleton) s1.clone();
System.out.println("是单例么?" + (s1 == s2));
}
}
如果想要比较简便的避免上诉的问题,最好的方式是使用枚举:
public enum SingleEnum {
INSTANCE;
public static SingleEnum getInstance(){
return INSTANCE;
}
}
其通过反射会抛出如下异常:
java.lang.NoSuchMethodException: com.price.effective.create.SingleEnum.<init>()
通过反序列化其也会返回INSTANCE对象。
其没有clone方法
综上,Enum可以作为想用单例时的第一选择。