用私有构造器或者枚举来强化Singleton属性

在1.5版本之前可以两种实现Singleton的方法,但是都要把构造器保存为私有的。

1、公有静态成员是个final域

 

 
  1. // Singleton with public final field - Page 17  
  2. public class Elvis {  
  3.     public static final Elvis INSTANCE = new Elvis();  
  4.     private Elvis() { }  
  5.   
  6.     public void leaveTheBuilding() {  
  7.         System.out.println("Whoa baby, I'm outta here!");  
  8.     }  
  9.   
  10.     // This code would normally appear outside the class!  
  11.     public static void main(String[] args) {  
  12.         Elvis elvis = Elvis.INSTANCE;  
  13.         elvis.leaveTheBuilding();  
  14.     }  
  15. }  

 

 

问题:但是客户端可以通过AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。
AccessibleObject 类是 Field、Method 和 Constructor 对象的基类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 Field、Method 或 Constructor 对象来设置或获得字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。 
在反射对象中设置 accessible 标志允许具有足够特权的复杂应用程序(比如 Java Object Serialization 或其他持久性机制)以某种通常禁止使用的方式来操作对象。 

2、公有的成员是个静态工厂方法
这种方法很清晰的表明了这个类是一个Singleton

 

 
  1. // Singleton with static factory - Page 17  
  2.   
  3. public class Elvis {  
  4.     private static final Elvis INSTANCE = new Elvis();  
  5.     private Elvis() { }  
  6.     public static Elvis getInstance() { return INSTANCE; }  
  7.   
  8.     public void leaveTheBuilding() {  
  9.         System.out.println("Whoa baby, I'm outta here!");  
  10.     }  
  11.   
  12.     // This code would normally appear outside the class!  
  13.     public static void main(String[] args) {  
  14.         Elvis elvis = Elvis.getInstance();  
  15.         elvis.leaveTheBuilding();  
  16.     }  
  17. }  

 

 

 

问题:同上

3、实现Singleton类变成可序列化的,仅仅实现序列化是不够的,为了维护并保证Singleton,必须声明所有实例域都是瞬时(transient)的,并提供一个readResolve方法。否则,每次反序列化一个序列的实例时,都会创建一个新的实例。
《The readResolve Method -- 序列化实现readResolve方法的作用》http://blog.csdn.net/partner4java/article/details/7058741

 

 

 
  1. import java.io.Serializable;  
  2.   
  3. // Serializable singleton with public final field - Page 18  
  4. public class Elvis implements Serializable{  
  5.     public static final Elvis INSTANCE = new Elvis();  
  6.     private Elvis() { }  
  7.   
  8.     public void leaveTheBuilding() {  
  9.         System.out.println("Whoa baby, I'm outta here!");  
  10.     }  
  11.   
  12.     private Object readResolve() {  
  13.         // Return the one true Elvis and let the garbage collector  
  14.         // take care of the Elvis impersonator.  
  15.         return INSTANCE;  
  16.     }  
  17.   
  18.     // This code would normally appear outside the class!  
  19.     public static void main(String[] args) {  
  20.         Elvis elvis = Elvis.INSTANCE;  
  21.         elvis.leaveTheBuilding();  
  22.     }  
  23. }  

 

 

 

4、从JDK 1.5开始,实现Singleton,可以编写一个包含单个元素的枚举类型。
而且可以解决复杂的反序列化或者反射的攻击。
单元素的枚举类型已经成为实现Singleton的最佳方法。

 

 
  1. // Enum singleton - the preferred approach - page 18  
  2. public enum Elvis {  
  3.     INSTANCE;  
  4.   
  5.     public void leaveTheBuilding() {  
  6.         System.out.println("Whoa baby, I'm outta here!");  
  7.     }  
  8.   
  9.     // This code would normally appear outside the class!  
  10.     public static void main(String[] args) {  
  11.         Elvis elvis = Elvis.INSTANCE;  
  12.         elvis.leaveTheBuilding();  
  13.     }  
  14. }  
  15. 1. 用私有构造器来强化

    很简单,就是将构造器声明为private类型的,但是需要注意一点,享有特权的客户端可以利用反射机制来调用到私有的构造器,为了进一步确保单例的唯一性,我们可以在私有的构造方法中判断唯一实例是否存在,存在的话抛出异常,就像这样:

    public class Singleton {
            private static final Singleton INSTANCE = new Singleton();
    
            private Singleton() {
                if (INSTANCE != null) {
                    throw new UnsupportedOperationException("Instance already exist");
                }
            }
    
            public static Singleton getInstance() {
                return INSTANCE;
            }
    }

    这样做就可以绝对防止出现多个实例了么?NO!
    现在还有一种情况下会出现多个实例,那就是在你序列化这个对象之后,在进行反序列化,这个时候,你将再次得到一个新的对象,不信?来吧,代码说明一切:

    1).首先将上面的一段代码实现Serializable接口:

    public class Singleton implements Serializable{
                            ...
    }

    2).开始进行序列化测试,测试代码如下(保证简洁好理解,不保证严谨性):

    public class SerializableTest {
            @Test
            public void serializableTest() throws Exception{
                serializable(Singleton.getInstance(), "test");
                Singleton singleton = deserializable("test");
                Assert.assertEquals(singleton, Singleton.getInstance());
            }
    
            //序列化
            private void serializable(Singleton singleton, String filename) throws IOException {
                FileOutputStream fos = new FileOutputStream(filename);
                ObjectOutputStream oos = new ObjectOutputStream(fos);
                oos.writeObject(singleton);
                oos.flush();
            }
            //反序列化
            @SuppressWarnings("unchecked")
            private <T> T deserializable(String filename) throwsIOException,
                                                    ClassNotFoundException {
                FileInputStream fis = new FileInputStream(filename);
                ObjectInputStream ois = new ObjectInputStream(fis);
                return (T) ois.readObject();
            }
    }

    3). 运行测试:
    啊偶~报错了,得到了两个不同的对象,看图:


    测试结果.png


    从结果中可以看出,得到了两个不同的对象,一个Singleton@deb6432和一个Singleton@1b4fb997;
    好吧,解决方案当然是有的,你只要在单例类里面加上下面这个方法:

    private Object readResolve() {
                return INSTANCE;
    }

    在运行一遍测试代码,问题解决!

    2. 利用枚举来强化Singleton(最佳方案)

    利用单元素的枚举来实现单例(Singleton),绝对防止多次实例化,上面的问题在这里都不会出现,实现代码如下:

    public enum SingletonEnum {
            INSTANCE;
    
            private String filed01;
    
            public String getFiled01() {
                return filed01;
            }
            public void setFiled01(String filed01) {
                this.filed01 = filed01;
            }
    }

    使用的时候跟我们常用的单例方式也十分相似:

    SingletonEnum.INSTANCE.setFiled01("123");
    System.out.println(SingletonEnum.INSTANCE.getFiled01());

    运行结果就不用贴出来了,大家都懂。



    作者:想飞的僵尸
    链接:http://www.jianshu.com/p/286231f0fa7c

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页