《Effective Java》第3条:用私有构造器或者枚举类型强化Singleton属性

原文地址: https://itweknow.cn/detail?id=49 , 欢迎大家访问。

实现单例模式的几种方法

书中一共提到了三种创建单例模式的方法:

  • 静态成员变量
  • 静态工厂方法
  • 单元素枚举
    其中前面两种也是我们经常使用的,书中也分析了这几种方式各自的优劣,下面我们就分别来看一下:

静态成员变量

public class Elvis01 {

    public static final Elvis01 INSTANCE = new Elvis01();

    private Elvis01() {}

    public void leaveTheBuilding() {
        System.out.println("leaving...");
    }
}

静态工厂方法

public class Elvis02 implements Serializable {

    private static final Elvis02 INSTANCE = new Elvis02();

    private Elvis02() {}

    public static Elvis02 getInstance() {
        return INSTANCE;
    }

}

但是这两种方式按照上面的写法都不能保证全局只有一个实例对象,我们且来看一下如何获取多个对象:

  • 通过反射机制
public class Test02 {
    public static void main(String[] args) throws Exception{
            Elvis02 e01 = Elvis02.getInstance();
            Elvis02 e02 = Elvis02.getInstance();
            System.out.println("e01的地址:" + e01);
            System.out.println("e02的地址:" + e02);
    
            // 通过反射来获取多个实例。
            Elvis02 e03 = null;
            Constructor[] constructors = e01.getClass().getDeclaredConstructors();
            AccessibleObject.setAccessible(constructors, true);
            for (int i=0; i< constructors.length; i++) {
                if (constructors[i].getParameterCount() == 0) {
                    // 无参构造器。
                    try {
                        e03 = (Elvis02) constructors[i].newInstance();
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("e03的地址:" + e03);
    }
}

上面这段代码的打印结果如下:

e01的地址:cn.gancy.item03.Elvis02@1540e19d
e02的地址:cn.gancy.item03.Elvis02@1540e19d
e03的地址:cn.gancy.item03.Elvis02@677327b6

可以看到e03的地址变了,也就证明我们通过反射机制成功获取了第二个对象。那么解决这个问题的方案书中也提到了,我们可以在私有构造方法中做一些特殊处理,当我们创建第二个对象的时候抛出异常即可。

  • 通过反序列化来获取对象
// 通过反序列化来获取多个实例。
Elvis02 e04 = null;
try (FileOutputStream fos = new FileOutputStream("test.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos)) {
    oos.writeObject(e01);
}
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"))) {
    e04 = (Elvis02) ois.readObject();
}
System.out.println("e04的地址:" + e04);

e04的地址如下所示

e01的地址:cn.gancy.item03.Elvis02@1540e19d
e02的地址:cn.gancy.item03.Elvis02@1540e19d
e03的地址:cn.gancy.item03.Elvis02@677327b6
e04的地址:cn.gancy.item03.Elvis02@2d98a335

可以发现在我们通过反序列化也得到了一个全新的实例e04,为了维护并且保证我们的单例模式,必须将我们的实例域都声明成瞬时的(transient),并且提供一个readResolve方法。修改完成后我们的Elvis变成如下所示:

public class Elvis03 implements Serializable {

    private transient static final Elvis03 INSTANCE = new Elvis03();

    private Elvis03() {}

    public static Elvis03 getInstance() {
        return INSTANCE;
    }

    private Object readResolve() {
        return INSTANCE;
    }
}

最后通过测试发现确实发序列化后并没有产生新的实例了。

单元素枚举

文中提到了第三种也是最推荐使用的一种实现单例模式的方法,即通过单元素枚举来实现单例模式,当然这种方式只能在jdk1.5之后使用。

public enum  Elvis04 {
    INSTANCE;

    public void leaveTheBuilding() {
        
    }
}

单元素枚举类型已经成为了实现单例模式的最佳方法,有下面几个优点:

  • 代码显得更为简洁
  • 无偿的提供了序列化机制
  • 可以绝对的防止多次实例化

猜你喜欢

转载自blog.csdn.net/sinat_27629035/article/details/84610865
今日推荐