下面代码是一个简易的单例模式,在类加载的时候就会调用私有构造方法创建一个INSTANCE。此时只要运行main函数就会加载Elvis类,即使main函数中一行代码也没有,控制台也会输出一句“调用了私有构造方法”。无论多用多少次 Elvis.INSTANCE 返回的都是同一个对象。
public class Elvis {
// 方式一:
public static final Elvis INSTANCE = new Elvis();
private Elvis(){
System.out.println("创建了一个Elvis对象");
}
}
但是我们依然可以利用反射技术来创建出多个 Elvis 对象:
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Elvis elvis1 = Elvis.INSTANCE;
Elvis elvis2 = Elvis.INSTANCE;
System.out.println(elvis1==elvis2);
Constructor<Elvis> constructor = Elvis.class.getDeclaredConstructor();
constructor.setAccessible(false); // 设置私有构造器的可访问性
Elvis elvis3 = constructor.newInstance(); // 调用私有构造器创建对象
System.out.println(elvis1==elvis3);
}
可以在被要求创建第二个实例的时候抛出异常进行防御:
private Elvis(){
if(INSTANCE !=null ){
try {
throw new Exception("防御");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
System.out.println("调用了私有构造方法");
}
使用静态工厂方法的Singleton:
public class Elvis {
// 方式二:
private static final Elvis INSTANCE = new Elvis();
private Elvis() {}
public static Elvis getInstance() {return INSTANCE;}
}
这种方式的优点书上写了,但是我看不懂,但是这种方法依然存在通过反射创建多个实例的问题。
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {
// 通过反序列化创建多个Singleton实例
Elvis elvis1 = Elvis.getInstance();
// 创建 ByteArrayOutputStream 对象来序列化对象到字节数组
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
// 将对象写入输出流
out.writeObject(elvis1);
// 关闭输出流
out.close();
byte[] serializedData = baos.toByteArray();
// 创建 ByteArrayInputStream 对象来反序列化字节数组为对象
ByteArrayInputStream bais = new ByteArrayInputStream(serializedData);
ObjectInputStream in = new ObjectInputStream(bais);
// 从输入流中读取对象
Elvis elvis2 = (Elvis) in.readObject();
// 关闭输入流
in.close();
System.out.println(elvis1==elvis2);
// 输出false,说明通过反序列化创建出来的实例和之前的实例不同
}
第三种创建单例的方式采用枚举的方法来实现,也是我们的首选方法:
package com.example.redisDemo.effective_java;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
public enum MyEnum {
INSTANCE;
public void leaveTheBuilding() {}
public static void main1(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
MyEnum myEnum1 = MyEnum.INSTANCE;
MyEnum myEnum2 = MyEnum.INSTANCE;
System.out.println(myEnum2==myEnum1);
// 创建 ByteArrayOutputStream 对象来序列化对象到字节数组
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
// 将对象写入输出流
out.writeObject(myEnum1);
// 关闭输出流
out.close();
byte[] serializedData = baos.toByteArray();
// 创建 ByteArrayInputStream 对象来反序列化字节数组为对象
ByteArrayInputStream bais = new ByteArrayInputStream(serializedData);
ObjectInputStream in = new ObjectInputStream(bais);
// 从输入流中读取对象
MyEnum myEnum3 = (MyEnum) in.readObject();
// 关闭输入流
in.close();
System.out.println(myEnum1==myEnum3);
}
public static void main(String[] args) throws Exception {
MyEnum instance1 = MyEnum.INSTANCE;
// 获取枚举类型 MyEnum 的 Class 对象
Class<?> enumClass = MyEnum.class;
// 使用反射获取 INSTANCE 实例
Object instance2 = Enum.valueOf((Class<MyEnum>) enumClass, "INSTANCE");
// 判断是否为 MyEnum 类型的枚举实例
if (enumClass.isInstance(instance2)) {
MyEnum myInstance = (MyEnum) instance2;
System.out.println(myInstance==instance1);
}
}
}
上述代码中main1函数两次输出结果均为true,main函数使用反射手段得到的instance也和MyEnum.INSTANCE用一个实例说明三者为同一个实例。