单例,枚举,反射,序列化--effectiveJava读书笔记

本文深入探讨了单例模式的破坏方法,包括序列化、反射、克隆等,并提出了使用枚举类型作为替代方案以确保单例模式的有效性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先看一个单例:

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可以作为想用单例时的第一选择。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值