浅谈enum与单例设计模式

在JDK1.5之前的单例实现方式有两种(懒汉式和饿汉式并无设计上的区别故看做一种),两者同是私有构
造器,导出静态成员变量,以便调用者访问。

第一种
package singleton;

public class Singleton {
        //导出全局成员
        public final static Singleton INSTANCE = new Singleton();
        //私有构造
        private Singleton(){}
}
 私有构造器只会被调用一次,用于构建Singleton类中的INSTANCE 实例,由于构造器被私有化,并且没
有其他公开的构造器,所有能够保证在app中只会有一个Singleton实例。

  真的是我们想的那样只会存在一个么?答案是否定的,可以使用java反射包提供的setAccessible()
方法去掉权限检查即可构造出实例对象

使用反射构造单例对象
      final Constructor<?>[] constructors = Singleton.class.getDeclaredConstructors();
                for (Constructor<?> constructor : constructors) {
                        //忽略检查
                        constructor.setAccessible(false);
                        //构造对象实例
                        final Object newInstance = constructor.newInstance();
                        
                        System.out.println(newInstance == Singleton.INSTANCE); //false
                }
 现在单例类被反射轻松攻破了吧。

 再看第二基于工厂方法的单例
package singleton;

public class Singleton {
        //导出全局成员
        private  final static Singleton INSTANCE = new Singleton();
        //私有构造
        private Singleton(){}
        public static final Singleton getInstance(){
                return INSTANCE;
        }
}
  该方式与第一种差别并不大,工厂方法的优势在于灵活性,在不改变API的前提下,我们可以修改该类
是否为单例,还是为每一个线程构建一个实例对象。同第一中方式相同也存在反射攻击的可能性,为了
防止反射攻击,需要对私有构造进行改写

package singleton;

import java.lang.reflect.Constructor;

public class Singleton {
        //导出全局成员
        public final static Singleton INSTANCE = new Singleton();
        //私有构造
        private Singleton(){
                if (null != INSTANCE) {
                        throw new IllegalArgumentException("不能存在两个实例对象");
                }
        }
        
        public static void main(String[] args) throws Exception {
                final Constructor<?>[] constructors = Singleton.class.getDeclaredConstructors();
                for (Constructor<?> constructor : constructors) {
                        //忽略检查
                        constructor.setAccessible(false);
                        //构造对象实例 此时这里回抛出异常
                        final Object newInstance = constructor.newInstance();
                        System.out.println(newInstance == Singleton.INSTANCE);
                }
        }
 这样子也可以就可以保证不受反射的攻击啦。
 
 在JDK1.5之后的版本提供了枚举关键字,提供了更加方便的单例创建方式
public enum Singleton {
        INSTANCE;
}
 三句代码搞定一个单例,并且无偿的提供序列化机制,绝对防止多实例的存在。
 以上两种方式如果序列化,仅仅是实现serialiazble接口是不够的,为了维护和保证Singleton请提供
一个方法
    public Singleton redResolve(){
                return INSTANCE;
        }
 

你可能感兴趣的:(java,单例)