单例模式

本文详细介绍了Java中实现单例模式的三种方法:饿汉式、工厂方法及枚举类型。探讨了每种方法的特点,并给出了示例代码。此外,还讨论了如何防御反射攻击,确保单例模式的有效性。

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

本文讲Java中单例模式的3种实现方法。

方法一

public class Elvis {
	public static final Elvis INSTANCE=new Elvis();
        private Elvis(){}
}

强制把构造函数私有化,只能通过INSTANCE来得到实例,并且这个实例只有1个。同时,INSTANCE中final类型,不可被更改。

但是客户端程序可以借助AccessibleObject.setAccessible的方法,通过反射来调用私有构造函数。代码如下:
public class Elvis {
	private static int count=0;
	public static final Elvis INSTANCE=new Elvis();
	private Elvis(){
		count++;
	}
	
	public static void main(String[] args){
		System.out.println(Elvis.INSTANCE.count);
		Class<?> cl=null;
		try {
			//通过反射得到类
			cl=Class.forName("singleton.Elvis");
			//获取类的构造函数
			Constructor constructors[]=cl.getDeclaredConstructors();
			//设置构造函数可访问
			constructors[0].setAccessible(true);
			//使用构造函数创建实例
			constructors[0].newInstance(null);
			//获取第一个成员(即count)的值
			System.out.println(cl.getDeclaredFields()[0].get(null));
			constructors[0].newInstance(null);
			System.out.println(cl.getDeclaredFields()[0].get(null));
		} catch (ClassNotFoundException |
				IllegalArgumentException |
				IllegalAccessException | 
				SecurityException |
				InstantiationException | 
				InvocationTargetException e) {
			e.printStackTrace();
		}
		
	}
}
输出:
1
2
3
java在反射创建一个类的实例时,默认会检测是否符合相关安全,该检测开关可以关闭。Constructor、Field、Method都是继承于AccessibleObject,对应实例调用setAccessible(true)就关闭该开关。
如果要抵御这种反射攻击,可以修改构造函数,让它在创建第二个实例时抛出异常。
private Elvis(){
		if(count==1){
			System.err.println("transgress singleton");
			System.exit(1);
		}
		count++;
	}

方法二

使用工厂方法。工厂方法本身在诸多好处,这里就不细讲了。
public class Elvis2 {
	private static final Elvis2 INSTANCE=new Elvis2();
	private Elvis2(){}
	public static Elvis2 getInstance(){
		return INSTANCE;
	}
	
	public static void main(String[] args){
		Elvis2 inst1=Elvis2.getInstance();
		Elvis2 inst2=Elvis2.getInstance();
		if(inst1==inst2)
			System.out.println("inst1==inst2");
		else
			System.out.println("inst1!=inst2");
	}
}

输出:inst1==inst2
当然这也会遇到反射攻击。

为了使Singeleton类变成可序列化的(Serializable),仅仅在声明中加上"implements Serializable"是不够的。为了维护并保证Singeleton,必须声明所有实例域都是瞬时的(transient),并提供一个readResolve方法,否则每次反序列化的实例时,都会创建一个新的实例。所以就有了第三种方法。

方法三

编写一个包含单个元素的枚举类型。
public enum Elvis3 {
	INSTANCE;
	public int count=0;
	public void incease(){
		count++;
	}
	
	public static void main(String[] args){
		Elvis3 inst1=Elvis3.INSTANCE;
		inst1.incease();
		inst1.incease();
		System.out.println(inst1.count);
		Elvis3 inst2=Elvis3.INSTANCE;
		System.out.println(inst2.count);
	}
}
单元素枚举类型已经成为实现Singeleton的最佳方式,它更加简洁,无偿提供了序列化机制,绝对防止多次实例化(即使使用复杂的序列化或反射攻击)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值