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

Singleton是指仅仅被实例化一次的类。Singleton通常被用来代表一个无状态的对象,如函数,或者那些本质上唯一的系统组件。

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。单例模式有5种(懒汉模式/饿汉模式/双锁检测模式/内部类/枚举类)。

实现Singleton有两种常见的方法。这两种方法都要保持构造器为私有的,并导出公有的静态成员,以便允许客户端能够访问该类的唯一实例

第一种方法,公有静态成员是一个final域:

package com.yhjt.singleton;

public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis(){
       //私有构造器只调用一次,用来初始化公有的静态final域Elvis.INSTANCE。
    }
    public void leaveTheBuilding(){
        
    }
}

第一种方法的优点:

  1. API清楚的表示这个类是一个Singleton:公有的静态域是final的,所以该域总是包含相同的对象引用
  2. 简单。

但是享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要防止这种情况,可以修改构造器,让他们在被要求创建第二个实例的时候抛出异常。

第二种方法:共有的成员是静态工厂方法

package com.yhjt.singleton;

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

    }
    //对与静态方法Elvis.getInstance的所有调用,都会返回同一个对象引用。
    public static Elvis getInstance(){
        return INSTANCE;
    }
    public void leaveTheBuilding(){

    }
}

当然这种方法也可能通过反射机制来创建第二个实例。

package com.yhjt.singleton;

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


public class Test {
    public static void main(String[] args) {
         Elvis instance1 = Elvis.getInstance();
         Elvis instance2 = Elvis.getInstance();
        System.out.println(instance2==instance1);//true
        //通过反射来获取一个不同的实例
        Elvis instance3 = null;
        Constructor[] constructors = instance1.getClass().getDeclaredConstructors();
        AccessibleObject.setAccessible(constructors,true);
        try {
            instance3 = (Elvis) constructors[0].newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        System.out.println("instance1的地址"+instance1);//instance1的地址com.yhjt.singleton.Elvis@1540e19d
        System.out.println("instance2的地址"+instance2);//instance2的地址com.yhjt.singleton.Elvis@1540e19d
        System.out.println("instance3的地址"+instance3);//instance3的地址com.yhjt.singleton.Elvis@677327b6

    }
}

第二种方法的优点:

  1. 灵活性。在不改变API的的前提下,我们可以改变改类是否应该为Singleton的想法。工厂方法返回的是一个唯一的实例,但是,它很容易被修改,比如修改为每个调用该方法的线程返回一个唯一的实例。
  2. 如果应用程序需要,可以写一个泛型Singleton工厂。
  3. 可以通过方法引用作为提供者,比如Elvis::instance就是一个Supplier<Elvis>

除了满足以上任意一种优势,否则还是优先考虑公有域方法

通过反序列化也可以获得一个新实例

        // 通过反序列化来获取多个实例。
        Elvis instance4 = null;
        try (FileOutputStream fos = new FileOutputStream("test.txt");
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            oos.writeObject(instance1);
        } catch (IOException e) {
            e.printStackTrace();
        }
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"))) {
            instance4 = (Elvis) ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("instance4的地址:" + instance4);

为了维护并且保证Singleton,必须将我们的实例域都声明成瞬时的(transient),并且提供一个readResolve方法。否则,每次反序列化一个序列化的实例时,都会创造一个实例。修改后的Elvis:

package com.yhjt.singleton;

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

    }
    public static Elvis getInstance(){
        return INSTANCE;
    }
    private Object readResolve() {
        return INSTANCE;
    }
    public void leaveTheBuilding(){

    }
}

实现Singleton的第三种方式是声明一个包含单个元素的枚举类型:

public enum  Elvis {
    INSTANCE;
    public void leaveTheBuilding() {
        
    }
}

虽然这种方法没有广泛采用,但是单元素枚举类型经常成为实现单例模式的最佳方法,有下面几个优点:

  • 代码更为简洁。
  • 无偿的提供了序列化机制。
  • 可以绝对的防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候。

注意,如果Singleton必须扩展一个超类,而不是扩展Enum的时候,则不宜使用这个方法(虽然可以声明枚举去实现接口)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值