• 问题

    针对Singleton,最简单的一种方式是:

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

    如果类被序列化了,不论是采用默认的序列化方式还是采用自定义的序列化方式,或者在readObject方法中进行所谓的处理,这个类都将不会是单例的了。那么针对这种要满足可序列化的单例应该怎样实现?

  • 答案

    要满足可序列化的单例,有两种方式:

    1. 利用readResolve方法:readResolve特性允许你用readObject创建的实例代替另一个实例。对于一个正在被反序列化的对象,如果它的类定义了一个readResolve方法,并且具备正确的声明,那么在反序列化之后,新建对象上的readResolve方法就会被调用。然后,该方法返回的对象引用将被返回,取代新建的对象。因此,在每次反序列化的时候,就可以在readResolve方法中返回之前的实例对象,这样就可以确保被多次反序列化后的对象也只会有一个。示例代码为:

      // readResolve for instance control - you can do better!

      private Object readResolve() {
          // Return the one true Elvis and let the garbage collector
          // take care of the Elvis impersonator.
          return INSTANCE;
      }
      

      该方法忽略了被反序列化的对象,只返回该类初始化时创建的那个特殊的Elvis实例。事实上,如果依赖readResolve进行实例控制,带有对象引用类型的所有实例域则都必须声明为transient的。否则,利用readResolve方法实现的单例也会遭受到攻击。

    2. 采用枚举实现:可以采用枚举实现可序列化的单例,这种安全性由JVM提供保障,而且代码十分简洁,实例域也不需要用transient修饰:

      // Enum singleton - the preferred approach

      public enum Elvis {
          INSTANCE;
          private String[] favoriteSongs ={ "Hound Dog", "Heartbreak Hotel" };
          public void printFavorites() {
              System.out.println(Arrays.toString(favoriteSongs));
          }
      }
      
  • 结论

    实现可序列化最简单安全的方式是采用枚举的形式,应该尽可能采用这种方式。如果采用readResolve实现的话,呀确保该类的所有实例域都为基本类型,或者是transient的。

results matching ""

    No results matching ""