Java:Effective java学习笔记之 用私有构造器或者枚举类型强化SIngleton属性

Posted JMW1407

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java:Effective java学习笔记之 用私有构造器或者枚举类型强化SIngleton属性相关的知识,希望对你有一定的参考价值。

强化SIngleton属性

1、Singleton模式

Hi,我们再来聊一聊Java的单例吧

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

第1种:懒汉式

/**
 * @description: 懒汉式----实现单例
 **/
public class SingleTonLanHan  
    /**
     *  私有化构造器
     */
    private SingleTonLanHan ()

    private static SingleTonLanHan  singleTonLanHan;

    public static  SingleTonLanHan getSingleTonLanHan()
        if (singleTonLanHan == null)
            singleTonLanHan = new SingleTonLanHan();
        
        return singleTonLanHan;
    


第2种:饿汉式

/**
 * @description: 饿汉式----实现单例
 **/
public class SingleTonEh 
    /**
     *  私有化构造器
     */
    private SingleTonEh()

    private static SingleTonEh singleTonEh = new SingleTonEh();

    public static SingleTonEh getSingleTonEh()
        return singleTonEh;
    

2、静态成员方式

public class Singleton 

    public static final Singleton singleton = new Singleton();

    private Singleton() 
        if (singleton != null)
            throw new RuntimeException("构造异常");
    

    public void doSomething()

在上述代码中,保持私有构造方法,提供一个静态的final类型的属性完成对象的初始化,在java中,享有特权的客户端可以通过反射机制 AccessibleObject.setAccessible 来调用私有构造器,所以为了防止出现这种问题,在私有化构造方法 保证第二次调用的时候抛出异常 ,来避免通过反射调用私有构造方法生成对象

3、用私有构造器来强化

很简单,就是将构造器声明为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();
        

报错了,得到了两个不同的对象
从结果中可以看出,得到了两个不同的对象,一个Singleton@deb6432和一个Singleton@1b4fb997;

好吧,解决方案当然是有的,你只要在单例类里面加上下面这个方法:

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

    private Elvis() 
    

    public void leaveTheBuilding() 
        System.out.println("Whoa baby, I'm outta here!");
    

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

    // This code would normally appear outside the class!
    public static void main(String[] args) 
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    

对于静态方法Elvis.getinstance()的所有调用,都会返回同一个对象引用,所以,永远不会创建其他的Elvis实例。

注意:

想让该类编程可序列化的。仅仅在类声明上添加implements Serializable是不够的,为了维护并保证Singleton ,必须声明所有的实例域都是瞬时(transient)的。

4、使用枚举实现单例模式

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

public enum Elvis 
    INSTANCE;

    public void leaveTheBuilding() 
        System.out.println("Whoa baby, I'm outta here!");
    

    // This code would normally appear outside the class!
    public static void main(String[] args) 
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    

参考

1、用私有构造器或者枚举类型强化SIngleton属性
2、《Effective Java》阅读笔记3 用私有构造器或者枚举类型强化Singleton属性
3、第三条:用私有构造器或者枚举类型强化Singleton属性

以上是关于Java:Effective java学习笔记之 用私有构造器或者枚举类型强化SIngleton属性的主要内容,如果未能解决你的问题,请参考以下文章

Java:Effective java学习笔记之 避免使用终结方法

Java:Effective java学习笔记之 消除过期对象引用

Java:Effective java学习笔记之 列表优先于数组

Java:Effective java学习笔记之 用enum代替int常量

Java:Effective java学习笔记之 复合优先于继承

Java:Effective java学习笔记之 接口优于抽象类