天天看点

【Effective java第二版整理】第三条: 用私有构造器或者枚举类型强化Singleton属性

Singleton指仅仅被实例化一次的类。Singleton通常被用来代表那些本质上唯一的系统组件。

实现单例模式常见有三种方式:

静态成员   

package Singleton强化;

public class Elvis {
    public  static final  Elvis INSTANCE=new Elvis();

    private Elvis() {
    }

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

    public static void main(String[] args) {
        Elvis elvis=new Elvis();
        elvis.leaveTheBuilding();
    }
}
      

静态工厂方法

package Singleton强化;

public class Elvis1 {

    private static final Elvis1 INSTANCE =new Elvis1();
    private Elvis1(){};
    public static Elvis1 getInstance(){return INSTANCE;};
    public void leaveTheBuilding(){
        System.out.println("Hello!...");
    }

    public static void main(String[] args) {
        Elvis1 elvis1=Elvis1.getInstance();
        elvis1.leaveTheBuilding();
    }
}      

   但是这两种方式不能完全保证全局唯一对象。享有特权的客户端可以借助反射机制设置AccessibleObject.setAccessible(ture),改变构造器访问属性,调用私有构造器。让它在被要求创建第二个实例的时候抛出异常。

package Singleton强化;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 第二次创建实例的时候抛出异常。
 */
public class Elvis1 {
    private  static int count=0; //记录创建次数
    private static final Elvis1 INSTANCE =new Elvis1();
    private Elvis1(){
        if (count>0){
            throw new IllegalArgumentException("Cannot create Elvis1 twice");
        }
        count++;
    }
    public static Elvis1 getInstance(){return INSTANCE;};
    public void leaveTheBuilding(){
        System.out.println("Hello!...");
    }

    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException {
        Elvis1 elvis1=Elvis1.getInstance();
       // elvis1.leaveTheBuilding();

        //手动反射去创建实例化对象
        Constructor<?> constructor = Elvis1.class.getDeclaredConstructors()[0];
        constructor.setAccessible(Boolean.TRUE);
        Elvis1 elvis11 = (Elvis1) constructor.newInstance();
        elvis11.leaveTheBuilding();
    }
}
      

   抛出不能创建两次的异常:

Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at Singleton强化.Elvis1.main(Elvis1.java:30)
Caused by: java.lang.IllegalArgumentException: Cannot create Elvis1 twice
	at Singleton强化.Elvis1.<init>(Elvis1.java:14)
	... 5 more
           

   如果上面两种实现的Singleton是可序列化的,加上implements Serializable只保证它是可序列化的,为了保证反序列化的时候,实例还是Singleton的,必须声明实例域都是瞬时(transient)的,并提供readResolve方法。否则,每次反序列化一个序列化实例,都会创建一个新的实例。下面的例子每次反序列化都会生成新的实例对象:

package Singleton强化;

import java.io.*;

public class Elvis implements Serializable{
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() {
    }

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

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Elvis elvis = new Elvis();
        //elvis.leaveTheBuilding();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:/1.6/elvis.txt  "));
        oos.writeObject(elvis);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:/1.6/elvis.txt  "));
        Elvis o = (Elvis) ois.readObject();
        ois.close();
        System.out.println(elvis == o); //false
    }
}
      

  加入readResolve方法之后:

package Singleton强化;

import java.io.*;

public class Elvis implements Serializable {
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() {
    }

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

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

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //E:/1.6/elvis.txt
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:/1.6/elvis.txt     "));
        oos.writeObject(elvis);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:/1.6/elvis.txt     "));
        Elvis o = (Elvis) ois.readObject();
        ois.close();
        System.out.println(elvis == o); //ture
    }
}
      

单元素枚举类型

单元素枚举:只需要编写一个包含单元素的枚举类型

package Singleton强化;

public enum Elvis2 {

    INSTANCE;

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

    public static void main(String[] args) {
        Elvis2 elvis2=Elvis2.INSTANCE;
        elvis2.leaveTheBuilding();
    }
}
      

通过枚举实现Singleton更加简洁,同时枚举类型无偿提供了序列化机制,可以防止序列化的时候多次实例化同一对象。枚举类型也可以防止反射攻击,当你试图反射实例化枚举实例的时候会抛出IllegalArgumentException异常。

总结

单元素的枚举类型已经成为实现Singleton的最佳方法。

继续阅读