美文网首页Effective Java学习
java代码优化——用私有构造器或者枚举类型强化Singleto

java代码优化——用私有构造器或者枚举类型强化Singleto

作者: Ruheng | 来源:发表于2017-01-20 10:34 被阅读66次

    Singleton只不过是指仅仅实例化一次的类。Singleton通常被用来代表那些本质上唯一的系统组件,比如窗口管理器或者文件系统。使类成为Singleton会使它的客户端测试变得十分困难,因为无法给Singleton替换模拟实现,除非它实现一个充当其类型的接口。

    单例的实现

    在[Java]1.5发行版本之前,实现Singleton有两种方法。这两种方法都要把构造器保持为私有的,并导出公有的静态成员,以便允许客户端能够访问该类的唯一实例。

    第一种:公有静态final成员
    public class Singleton {
        public static final Singleton INSTANCE = new Singleton();
        private Singleton() {
            // 初始化操作
        }
        public void execute() {
            System.out.println("execute Singleton");
        }
    } 
    

    但是,这种写法可以通过反射机制调用私有的构造器。

    Class singletonClass = Class.forName("effactive.java.eff003.Singleton");
    Constructor constructor = singletonClass.getDeclaredConstructor();
    constructor.setAccessible(Boolean.TRUE);
    Singleton singleton = (Singleton) constructor.newInstance();
    singleton.execute();
    

    为了避免反射机制调用私有的构造器需要在修改私有的构造器,当试图创建第二个实例是抛出异常。

    private Singleton() {
        if (INSTANCE != null){
            throw new IllegalStateException("Already instantiated");
        }
    }
    

    这样,在创建第二个实例是就会抛出异常。保证始终只有一个实例。

    第二种:公有的静态工厂方法
    public class Singleton2 {
        private static final Singleton2 INSTANCE = new Singleton2();
        private Singleton2() {
            if (INSTANCE != null){
                throw new IllegalStateException("Already instantiated");
            }
        }
        public static Singleton2 getInstance(){
            return  INSTANCE;
        }
        public void execute() {
            System.out.println("execute Singleton2");
        }
    }
    

    静态工厂方法要比第一种公有静态final成员灵活一些。可以在不改变API的前提下,改变该类是否是单例的想法。但是,这种写法仍可以通过反射机制调用私有的构造器。

    在Java 1.5之后我们有第三种。

    第三种:单个元素的枚举类型

    public enum Singleton3 {
        INSTANCE;
        public void execute() {
            System.out.println("execute Singleton3");
        }
    }
    

    使用的话:

    Singleton3.INSTANCE.execute();
    

    由于Java的枚举类型实现了Serializable接口,默认是可以序列化的,而且还能包证反序列化之后不会重新创建一个实例。

    单例的序列化

    如果我们将单例序列化,那么当我们反序列化,还会单例吗?

    对于第一种和第二种来说,反序列化之后,我们相当与重新创建了一个新的实例。不能再保证单例了。

    对于第三种,由于JAVA在枚举类型反序列化时候与一般类的不一样,可以保证反序列化之后的依然是单例。
    下面我们来解决第一种和第二种反序列化的问题。

    public class Singleton4 implements Serializable{
        private static final Singleton4 INSTANCE = new Singleton4();
        private Singleton4() {
            if (INSTANCE != null){
                throw new IllegalStateException("Already instantiated");
            }
        }
        public static Singleton4 getInstance(){
            return  INSTANCE;
        }
       //需要该方法来保证反序列化后仍为同一对象
        private Object readResolve() {
            return Singleton4.INSTANCE;
        }
        public void execute() {
            System.out.println("execute Singleton4");
        }
    }
    

    下面是测试的代码

    File file = new File("/home/pj/person.out");
    ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
    oout.writeObject(Singleton4.getInstance());
    oout.close();
    ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
    Singleton4 singleton4 = (Singleton4) oin.readObject();
    oin.close();
    System.out.println(Singleton4.getInstance());
    System.out.println(singleton4);
    System.out.println(Singleton4.getInstance() == singleton4);
    

    总结:

    对比来看,单元素的枚举类型应该是实现单例的最佳方式了。

    相关文章

      网友评论

        本文标题:java代码优化——用私有构造器或者枚举类型强化Singleto

        本文链接:https://www.haomeiwen.com/subject/fvywbttx.html