四、单例设计模式
4.1 创建型设计模式简介
创建型模式的主要关注点是怎样创建对象,它的主要特点是将对象的创建与使用分离。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成。就像我们去商场购买商品时,不需要知道商品是怎么生产出来一样,因为它们由专门的厂商生产。
创建型模式分为以下5种。
- 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
- 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
- 工厂方法(FactoryMethod)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
- 抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
- 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
以上 5 种创建型模式,除了工厂方法模式属于类创建型模式,其他的全部属于对象创建型模式,我们将在之后的教程中详细地介绍它们的特点、结构与应用。
4.2 单例设计模式简介
- 单例(Singleton)模式:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
单例是一种常用的设计模式,属于创建型设计模式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:
- 1)某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。单例能够降低系统内存的使用频率,减轻GC压力。
- 2)有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
4.3 单例设计模式的实现
Singleton 模式通常有饿汉式和懒汉式两种实现形式;
4.3.1 饿汉式
- 单例类:
package com.pattern.demo01_饿汉式; /** * @author lscl * @version 1.0 * @intro: */ public class Singleton { // 私有化成员变量(在类加载的时候就已经创建了) private static Singleton singleton=new Singleton(); // 私有化构造(不允许new) private Singleton(){} // 对外提供公共的获取方法 public static Singleton getSingleton(){ return singleton; } }
- 测试类:
package com.pattern.demo01_饿汉式; /** * @author lscl * @version 1.0 * @intro: */ public class Demo01 { public static void main(String[] args) throws Exception { Singleton s1 = Singleton.getSingleton(); Singleton s2 = Singleton.getSingleton(); System.out.println(s1 == s2); } }
饿汉式单例写法在类加载的时候就已经创建好了该类的单例对象;一般用于单例对象较少的情况下,饿汉式有一个明显的缺点,就是所有对象在类加载的时候就实例化,而不是等到使用的时候再实例化,这样一来如果系统中有大量的单例对象存在,则在系统初始化时会造成大量的内存浪费;不管对象是否使用,都占着内存空间;
4.3.2 懒汉式
懒汉式也是实现单例设计模式中的一种方式;
- 单例类:
package com.pattern.demo01_单例设计模式的实现; /** * @author lscl * @version 1.0 * @intro: */ public class Singleton { // 定义成员变量 private static Singleton singleton = null; // 私有化构造方法 private Singleton() { } // 提供方法供外界访问 public static Singleton getSingleton() { if (singleton != null) { return new Singleton(); } return null; } }
Tips:需要用到的时候(调用方法的时候)再创建
- 测试类:
package com.pattern.demo01_单例设计模式的实现; /** * @author lscl * @version 1.0 * @intro: */ public class Demo01 { public static void main(String[] args) { Singleton s1 = Singleton.getSingleton(); Singleton s2 = Singleton.getSingleton(); System.out.println(s1 == s2); // true } }
相对比与饿汉式,懒汉式在使用到单例对象时才创建对象,相比于饿汉式节约了内存;
4.4 线程安全与单例设计模式
4.4.1 线程安全问题
为了解决饿汉式带来的内存浪费问题,我们使用懒汉式的单例设计模式写法,其特点是单例对象在使用时才会被实例化,但是这会引来一个新的问题——线程安全问题;如果在多线程环境下,那么可能会导致类被实例化多次;
- 修改单例类:
package com.pattern.demo03_懒汉式_线程安全问题; /** * @author lscl * @version 1.0 * @intro: */ public class Singleton { private static Singleton singleton = null; public static Singleton getSingleton() { if (singleton == null) { try { Thread.sleep(10); // 为了更加的方便看出效果,这里睡眠10ms } catch (InterruptedException e) { e.printStackTrace(); } singleton = new Singleton(); } return singleton; } }
- 测试类:
package com.pattern.demo03_懒汉式_线程安全问题; /** * @author lscl * @version 1.0 * @intro: */ public class Demo01 { public static void main(String[] args) { new Thread(){ @Override public void run() { Singleton singleton = Singleton.getSingleton(); System.out.println(Thread.currentThread().getName() + "【" + singleton + "】"); } }.start(); new Thread(){ @Override public void run() { Singleton singleton = Singleton.getSingleton(); System.out.println(Thread.currentThread().getName() + "【" + singleton + "】"); } }.start(); new Thread(){ @Override public void run() { Singleton singleton = Singleton.getSingleton(); System.out.println(Thread.currentThread().getName() + "【" + singleton + "】"); } }.start(); } }
执行结果:
4.4.2 解决线程安全问题
为了解决线程安全问题,我们可以使用synchronized关键字来实现线程同步;
package com.pattern.demo04_懒汉式_synchronized; /** * @author lscl * @version 1.0 * @intro: */ public class Singleton { private static Singleton singleton = null; public static Singleton getSingleton() { synchronized (Singleton.class){ // 关键的代码锁住(保证同一时间只能有一个线程进来判断) if (singleton == null) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } singleton = new Singleton(); } return singleton; } } }
- 测试类(代码不变):
package com.pattern.demo04_懒汉式_synchronized; /** * @author lscl * @version 1.0 * @intro: */ public class Demo01 { public static void main(String[] args) { new Thread(){ @Override public void run() { Singleton singleton = Singleton.getSingleton(); System.out.println(Thread.currentThread().getName() + "【" + singleton + "】"); } }.start(); new Thread(){ @Override public void run() { Singleton singleton = Singleton.getSingleton(); System.out.println(Thread.currentThread().getName() + "【" + singleton + "】"); } }.start(); new Thread(){ @Override public void run() { Singleton singleton = Singleton.getSingleton(); System.out.println(Thread.currentThread().getName() + "【" + singleton + "】"); } }.start(); } }
再次运行测试类:发现多条线程创建的对象仍是同一个
4.4.3 双重校验锁方案
我们前面通过synchronized同步代码块可以实现多线程下同步问题;但是如果在线程数量非常大的情况下,使用synchronized加锁,则会导致大量的线程阻塞,程序性能会大幅下降;想要保证线程安全问题,有想要性能上的提升,我们可以使用双重校验锁;
- synchronized方案:
- 未进行双重校验:
- 双重校验:
- 双重校验锁判断:
package com.pattern.demo05_双重校验锁; /** * @author lscl * @version 1.0 * @intro: */ public class Singleton { private static Singleton singleton = null; public static Singleton getSingleton() { // 检查是否需要阻塞(并不是所有的线程上来就阻塞) if (singleton == null) { // 获取到锁的线程才进去 synchronized (Singleton.class){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 重新判断,因为前面有可能多条线程都通过了if判断 if(singleton==null){ singleton = new Singleton(); } } } return singleton; } }
- 测试类(代码不变):
package com.pattern.demo05_双重校验锁; /** * @author lscl * @version 1.0 * @intro: */ public class Demo01 { public static void main(String[] args) { new Thread(){ @Override public void run() { Singleton singleton = Singleton.getSingleton(); System.out.println(Thread.currentThread().getName() + "【" + singleton + "】"); } }.start(); new Thread(){ @Override public void run() { Singleton singleton = Singleton.getSingleton(); System.out.println(Thread.currentThread().getName() + "【" + singleton + "】"); } }.start(); new Thread(){ @Override public void run() { Singleton singleton = Singleton.getSingleton(); System.out.println(Thread.currentThread().getName() + "【" + singleton + "】"); } }.start(); } }
再次运行测试类:发现多条线程创建的对象仍是同一个
4.5 静态内部类与单例设计模式
4.5.1 静态内部类方案
双重校验锁单例写法虽然解决了线程安全问题和性能问题,但是只要用到synchronized关键字总是要加锁,对性能还是存在一定的影响;我们可以从类加载的角度初始化,采用静态内部类来实现单例设计模式;
- 小案例:
package com.pattern.demo06_静态内部类; /** * @author lscl * @version 1.0 * @intro: 测试静态内部类加载顺序 */ public class TestInnerClass { public static class StaticInnerTest { static { System.out.println("静态内部类加载了...."); } static void staticInnerMethod() { System.out.println("静态内部类方法调用了...."); } } }
- 测试类:
package com.pattern.demo06_静态内部类; /** * @author lscl * @version 1.0 * @intro: */ public class Demo01_静态内部类加载顺序 { public static void main(String[] args) { // 创建外部类时,静态内部类不会随着外部类的加载而加载 TestInnerClass testInnerClass = new TestInnerClass(); System.out.println("======================="); TestInnerClass.StaticInnerTest.staticInnerMethod(); } }
输出结果:
======================= 静态内部类加载了.... 静态内部类方法调用了....
从输出结果可以得出结论:加载一个类时,其内部类不会被同时加载。
Tips:类何时被加载?
- 1)实例化对象
- 2)创建了子类的实例
- 3)访问静态方法、静态变量等
- 4)获取字节码对象的时候
- 1)对象.getClass()
- 2)调用类的class属性
- 3)class.forName()
- 5)加载了子类(子类调用静态成员、new对象等情况)
知道了静态内部类的加载顺序后,我们可以利用这一特点来实现懒汉式单例设计模式
- 单例类:
package com.pattern.demo06_静态内部类; /** * @author lscl * @version 1.0 * @intro: */ public class Singleton { // 私有化成员 private static Singleton singleton = null; // 私有构造方法 private Singleton() {} // 提供公共的访问方法 public static Singleton getSingleton(){ return Holder.INSTANCE; } // 使用静态内部类来初始化 private static class Holder { private static final Singleton INSTANCE = new Singleton(); } }
- 测试:
package com.pattern.demo06_静态内部类; /** * @author lscl * @version 1.0 * @intro: */ public class Demo02_静态内部类实现单例设计模式 { public static void main(String[] args) { Singleton s1 = Singleton.getSingleton(); // 在这一步实例化了单例类 Singleton s2 = Singleton.getSingleton(); System.out.println(s1 == s2); // true } }
Tips:静态内部类方案不会存在线程安全问题;静态内部类单例模式是一种优秀的单例模式,是项目中比较常用的一种单例模式(即使存在后续的问题)。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
4.5.2 反射破坏单例设计模式
我们之前的设计方案不管是饿汉式还是懒汉式都是将单例类的构造方法私有,让外部无法直接通过new关键字来实例化对象;但是我们之前学习过反射,通过反射我们可以在外部访问任意修饰符修饰的任意方法;也就是说,我们之前设计的单例方案不管哪一种都可以使用反射来破解;
- 反射破解静态内部类方案:
package com.pattern.demo06_静态内部类; import java.lang.reflect.Constructor; /** * @author lscl * @version 1.0 * @intro: */ public class Demo03_反射破坏静态内部类单例设计模式 { public static void main(String[] args) throws Exception { // 获取任意修饰符修饰的构造方法 Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(); // 设置强制访问 constructor.setAccessible(true); Singleton s1 = constructor.newInstance(); // 相当于new Singleton s2 = constructor.newInstance(); System.out.println(s1 == s2); // false } }
Tips:这里只演示了破坏静态内部类方式的单例设计模式,反射可以破坏我们前面学习过的任意单例设计模式;
4.5.3 防止反射破坏单例
- 重新改进单例类:
package com.pattern.demo07_静态内部类防止反射破坏单例设计模式; import java.io.Serializable; /** * @author lscl * @version 1.0 * @intro: */ public class Singleton implements Serializable { // 私有化成员 private static Singleton singleton = null; // 私有化构造方法 private Singleton() { if (Holder.INSTANCE != null) { throw new RuntimeException("不允许创建多个实例"); // 如果通过构造方法来创建对象则抛出异常 } } // 共有访问方法 public static Singleton getSingleton() { return Holder.INSTANCE; } // 静态内部类 private static class Holder { private static final Singleton INSTANCE = new Singleton(); } }
- 测试类1:
package com.pattern.demo07_静态内部类防止反射破坏单例; import java.lang.reflect.Constructor; /** * @author lscl * @version 1.0 * @intro: */ public class Demo01_改进反射破解单例设计模式 { public static void main(String[] args) throws Exception{ // 获取任意修饰符修饰的构造方法 Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(); // 设置强制访问 constructor.setAccessible(true); Singleton s1 = constructor.newInstance(); // 抛出异常,不允许通过构造方法来创建对象 Singleton s2= constructor.newInstance(); System.out.println(s1 == s2); } }
4.6 序列化与单例设计模式
我们单例对象创建好后,有时候需要将对象序列化到磁盘,这样就可以在文件系统中存储它的状态。反序列化的对象会重新分配内存,即被重新创建。这样则违背了单例模式的初衷,相当于破坏了单例设计模式;
4.6.1 反序列化破坏单例设计模式
- 静态内部类方式的单例类:
package com.pattern.demo08_序列化破坏单例设计模式; import java.io.Serializable; /** * @author lscl * @version 1.0 * @intro: */ public class Singleton implements Serializable { // 实现Serializable接口 // 私有化成员 private static Singleton singleton = null; // 私有化构造方法 private Singleton() { System.out.println("执行了几次构造方法????"); if (Holder.INSTANCE != null) { throw new RuntimeException("不允许创建多个实例"); // 如果通过构造方法来创建对象则抛出异常 } } // 共有访问方法 public static Singleton getSingleton() { return Holder.INSTANCE; } // 静态内部类 private static class Holder { private static final Singleton INSTANCE = new Singleton(); } }
- 测试类:
package com.pattern.demo08_序列化破坏单例设计模式; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /** * @author lscl * @version 1.0 * @intro: */ public class Demo01 { public static void main(String[] args) { try ( // 对象输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser")); // 对象输入流 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser")); ) { // 获取单例对象 Singleton s1 = Singleton.getSingleton(); // 写出对象 oos.writeObject(s1); // 反序列化底层并不是空参执行构造方法,因此Singleton对象被创建了两次 Singleton s2 = (Singleton) ois.readObject(); System.out.println(s1 == s2); // false,违反单例设计模式 } catch (Exception e) { e.printStackTrace(); } } }
4.6.2 解决反序列化破坏单例设计模式
从之前运行的结果可以发现,反序列化后的对象与之前序列化到文件中的对象并不是同一个,Singleton对象被实例化了两次,;我们如何保证在序列化的情况下也能实现单例设计模式呢?我们可以在单例对象中添加一个readResolve()方法,该方法的返回值是对象被反序列化时返回值;
- 修改单例类:
package com.pattern.demo09_解决反序列化破坏单例; import java.io.Serializable; /** * @author lscl * @version 1.0 * @intro: */ public class Singleton implements Serializable { // 实现Serializable接口 // 私有化成员 private static Singleton singleton = null; // 私有化构造方法 private Singleton() { System.out.println("执行了几次构造方法????"); if (Holder.INSTANCE != null) { throw new RuntimeException("不允许创建多个实例"); // 如果通过构造方法来创建对象则抛出异常 } } // 共有访问方法 public static Singleton getSingleton() { return Holder.INSTANCE; } // 静态内部类 private static class Holder { private static final Singleton INSTANCE = new Singleton(); } // 该对象被反序列化时返回的对象 private Object readResolve() { return Holder.INSTANCE; } }
- 再次运行反序列化测试代码,发现能保证单例;
4.6.3 探究反序列化源码
为什么在对象里面添加了readResolve()方法就可以保证对象的单例呢?这得从序列化的源码开始说起....
- 1)我们点开ObjectInputStream类的readObject()方法:
public final Object readObject() throws IOException, ClassNotFoundException { if (enableOverride) { return readObjectOverride(); } // if nested read, passHandle contains handle of enclosing object int outerHandle = passHandle; try { Object obj = readObject0(false); // 核心方法 handles.markDependency(outerHandle, passHandle); ClassNotFoundException ex = handles.lookupException(passHandle); if (ex != null) { throw ex; } if (depth == 0) { vlist.doCallbacks(); } return obj; } finally { passHandle = outerHandle; if (closed && depth == 0) { clear(); } } }
- 2)点开readObject0()方法:
private Object readObject0(boolean unshared) throws IOException { ... 代码省略 depth++; totalObjectRefs++; try { switch (tc) { case TC_NULL: return readNull(); case TC_REFERENCE: return readHandle(unshared); case TC_CLASS: return readClass(unshared); case TC_CLASSDESC: case TC_PROXYCLASSDESC: return readClassDesc(unshared); case TC_STRING: case TC_LONGSTRING: return checkResolve(readString(unshared)); case TC_ARRAY: return checkResolve(readArray(unshared)); case TC_ENUM: return checkResolve(readEnum(unshared)); case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared)); // 反序列化对象时执行的方法(核心方法) case TC_EXCEPTION: IOException ex = readFatalException(); throw new WriteAbortedException("writing aborted", ex); ... 代码省略 } } finally { depth--; bin.setBlockDataMode(oldMode); } }
- 3)打开readOrdinaryObject方法:
private Object readOrdinaryObject(boolean unshared) throws IOException { if (bin.readByte() != TC_OBJECT) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false); desc.checkDeserialize(); Class<?> cl = desc.forClass(); if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) { throw new InvalidClassException("invalid class descriptor"); } Object obj; try { // 判断有没有无惨构造方法,如果是无惨构造方法就实例化对象 obj = desc.isInstantiable() ? desc.newInstance() : null; } catch (Exception ex) { throw (IOException) new InvalidClassException( desc.forClass().getName(), "unable to create instance").initCause(ex); } passHandle = handles.assign(unshared ? unsharedMarker : obj); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(passHandle, resolveEx); } if (desc.isExternalizable()) { readExternalData((Externalizable) obj, desc); } else { readSerialData(obj, desc); } handles.finish(passHandle); if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) // 核心代码 { Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { // Filter the replacement object if (rep != null) { if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1); } } handles.setObject(passHandle, obj = rep); } } return obj; }
4)hasReadResolveMethod源码:
boolean hasReadResolveMethod() { requireInitialized(); return (readResolveMethod != null); }
简单的判断一下readResolveMethod变量是否为空,那么readResolveMethod是在哪里被赋值的呢?
5)通过查看源码得知,readResolveMethod在ObjectStreamClass的自由构造方法中被赋值:
readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);
6)getInheritableMethod源码如下:
private static Method getInheritableMethod(Class<?> cl, String name, Class<?>[] argTypes, Class<?> returnType) { Method meth = null; Class<?> defCl = cl; while (defCl != null) { try { meth = defCl.getDeclaredMethod(name, argTypes); break; } catch (NoSuchMethodException ex) { defCl = defCl.getSuperclass(); } } if ((meth == null) || (meth.getReturnType() != returnType)) { return null; } meth.setAccessible(true); int mods = meth.getModifiers(); if ((mods & (Modifier.STATIC | Modifier.ABSTRACT)) != 0) { return null; } else if ((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) { return meth; } else if ((mods & Modifier.PRIVATE) != 0) { return (cl == defCl) ? meth : null; } else { return packageEquals(cl, defCl) ? meth : null; } }
上面代码的逻辑其实就是通过反射找到一个readResolve方法,之后保存下来;赋值给readResolveMethod
7)再回到ObjectInputStream的readOrdinaryObject方法:
private Object readOrdinaryObject(boolean unshared) throws IOException { if (bin.readByte() != TC_OBJECT) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false); desc.checkDeserialize(); Class<?> cl = desc.forClass(); if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) { throw new InvalidClassException("invalid class descriptor"); } Object obj; try { obj = desc.isInstantiable() ? desc.newInstance() : null; } catch (Exception ex) { throw (IOException) new InvalidClassException( desc.forClass().getName(), "unable to create instance").initCause(ex); } passHandle = handles.assign(unshared ? unsharedMarker : obj); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(passHandle, resolveEx); } if (desc.isExternalizable()) { readExternalData((Externalizable) obj, desc); } else { readSerialData(obj, desc); } handles.finish(passHandle); if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); // 最终返回的是desc.invokeReadResolve方法的返回值 if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { // Filter the replacement object if (rep != null) { if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1); } } handles.setObject(passHandle, obj = rep); } } return obj; }
8)desc.invokeReadResolve
源码如下:
Object invokeReadResolve(Object obj) throws IOException, UnsupportedOperationException { requireInitialized(); if (readResolveMethod != null) { try { return readResolveMethod.invoke(obj, (Object[]) null); // 将readResolveMethod封装的方法执行 } catch (InvocationTargetException ex) { Throwable th = ex.getTargetException(); if (th instanceof ObjectStreamException) { throw (ObjectStreamException) th; } else { throwMiscException(th); throw new InternalError(th); // never reached } } catch (IllegalAccessException ex) { // should not occur, as access checks have been suppressed throw new InternalError(ex); } } else { throw new UnsupportedOperationException(); } }
这个方法的逻辑就是将readResolveMethod
封装的方法执行,readResolveMethod
封装的方法正是我们之前查看的readResolve
方法;也就是说,最终反序列化返回的对象是这个对象readResolve方法的返回值;
4.7 枚举保证单例设计模式
4.7.1 设计枚举单例类
- 枚举单例类:
package com.pattern.demo10_枚举设计单例; /** * @author lscl * @version 1.0 * @intro: */ public enum Singleton { INSTANCE; public static Singleton getSingleton() { return INSTANCE; } }
- 测试枚举单例类:
package com.pattern.demo10_枚举设计单例; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /** * @author lscl * @version 1.0 * @intro: */ public class Demo01 { public static void main(String[] args) throws Exception { Singleton s1 = Singleton.INSTANCE; // 测试反射 Class<Singleton> clazz = Singleton.class; // Singleton s3 = clazz.newInstance(); // 出现异常: java.lang.NoSuchMethodException // 测试序列化 try ( // 对象输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser")); // 对象输入流 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser")); ) { // 写出对象 oos.writeObject(s1); // 反序列化 Singleton s2 = (Singleton) ois.readObject(); System.out.println(s1 == s2); // true,枚举能够保证对象在反序列化时的一致 } catch (Exception e) { e.printStackTrace(); } } }
4.7.2 为什么枚举单例对象不会被破坏?
- 1)探究为什么反射不会破坏枚举单例对象:
在Enum类中定义着如下的构造方法:
protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; }
意味着所有的枚举类都会有一个这样的构造方法(空参构造方法将会被擦除)
我们尝试反射其有参构造方法:
package com.pattern.demo10_枚举设计单例; import java.lang.reflect.Constructor; /** * @author lscl * @version 1.0 * @intro: */ public class Demo02_探究反射破坏枚举单例对象 { public static void main(String[] args) throws Exception{ // 获取指定的构造器 Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(String.class, int.class); // 设置强制访问 constructor.setAccessible(true); // 创建对象 Singleton singleton = constructor.newInstance("xxx", 110); } }
运行程序出现如下错误:
Tips:在Java的反射机制中,不允许对枚举类进行反射;
- 2)探究为什么反序列化不会破坏枚举单例对象?
关于反序列化的代码都在ObjectInputStream类的readEnum方法中:
private Enum<?> readEnum(boolean unshared) throws IOException { if (bin.readByte() != TC_ENUM) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false); if (!desc.isEnum()) { throw new InvalidClassException("non-enum class: " + desc); } int enumHandle = handles.assign(unshared ? unsharedMarker : null); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(enumHandle, resolveEx); } String name = readString(false); Enum<?> result = null; Class<?> cl = desc.forClass(); if (cl != null) { try { @SuppressWarnings("unchecked") Enum<?> en = Enum.valueOf((Class)cl, name); // 核心代码 result = en; } catch (IllegalArgumentException ex) { throw (IOException) new InvalidObjectException( "enum constant " + name + " does not exist in " + cl).initCause(ex); } if (!unshared) { handles.setObject(enumHandle, result); } } handles.finish(enumHandle); passHandle = enumHandle; return result; }
通过标注的核心代码可知,枚举类型的反序列化实质上就是通过Enum类的valueOf方法获取枚举类的唯一实例;