Singleton指只有一个实例的类,只能被创建一次。

在Java1.5之前实现Singleton有两种方式,都是将构造器设为private并导出公有的静态成员实例。

第一种方式将公有的静态成员实例设为final:

public class Singleton {

    public static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

}

私有构造器仅被调用一次,用来实例化公有的静态final属性INSTANCE,由于缺少对外暴露的构造器所以保证INSTANCE全局唯一。不过如果考虑到反射,其实客户端还是会生成多个实例,客户端可以通过反射获取Constructor并调用Constructor.setAccessible(true),接着Constructor.newInstance()也是可以生成多个实例的。如果需要防止出现这种情况,通过修改构造器可以在被要求创建第二个实例时抛出异常:

 public class Singleton {

     private static int i = 0;

     public static final Singleton INSTANCE = new Singleton();

     private Singleton() {
i++;
if (i > 1)
throw new RuntimeException("单例,不允许创建多个");
} public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException {
Singleton s = Singleton.INSTANCE; // 第一次
Class<Singleton> clz = Singleton.class;
clz.newInstance(); // 第二次, Class.newInstance底层调用的还是Constructor.newInstance
Constructor[] cs = clz.getDeclaredConstructors(); // 或者直接尝试使用构造器
for (Constructor c : cs) {
c.setAccessible(true);
c.newInstance();
}
} }
Exception in thread "main" java.lang.RuntimeException: 单例,不允许创建多个
at Singleton.<init>(Singleton.java:20)
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 java.lang.Class.newInstance(Class.java:442)
at Singleton.main(Singleton.java:26)

 第二种方式是提供公有的静态工厂方法:

public class Singleton {

    private static final Singleton INSTANCE = new Singleton();

    public static Singleton getInstance() {
return INSTANCE;
} private Singleton() {}
}

这种方式的优势是更灵活,在不改变API的前提下,如果不想返回单例也可以每次都new新的实例。

上述两种方式如果让Singleton类变成可序列化(implements Serializable),那么在反序列化时就会破坏单例性,因为反序列化时会创建一个新实例。为了保证在implements Serializable的前提下仍能保证单例那么就需要做两件事,第一将Singleton中所有属性都声明为transient,第二提供一个readResolve方法:

private Object readResolve() {
return INSTANCE;
}

提供上面的readResolve方法后,在反序列化成功后会调用readResolve方法得到INSTANCE并用INSTANCE替换调刚刚反序列化得到的新实例,新实例将会被接下里的GC操作回收,从而保证了单例性。

jdk1.5以后利用枚举又有了第三种实现单例的方式,利用包含单个元素的枚举:

public enum Singleton {

    INSTANCE;

    public void doSomething() {}
}

这种方式与公有域(第一种方式)方法类似,不过更加简洁,并默认提供序列化并防止多次实例化。是实现单例的最佳方法。

下面尝试使用反射和序列化测试使用枚举实现的单例

反射:

 public class Main {

     public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException {
Class<Singleton> clz = Singleton.class; // 1.使用Class.newInstance
clz.newInstance(); } }
Exception in thread "main" java.lang.InstantiationException: Singleton
at java.lang.Class.newInstance(Class.java:427)
at Main.main(Main.java:15)
Caused by: java.lang.NoSuchMethodException: Singleton.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.newInstance(Class.java:412)
... 1 more
 public class Main {

     public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException {
Class<Singleton> clz = Singleton.class; // 2.直接使用构造器
Constructor[] constructors = clz.getDeclaredConstructors();
for (Constructor constructor : constructors) {
constructor.setAccessible(true);
Singleton singleton = (Singleton) constructor.newInstance();
} } }
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at Main.main(Main.java:22)

通过上面测试可以验证通过反射没法创建新的实例,所以使用枚举实现的单例有效的防止了放射。

序列化:

public enum Singleton implements Serializable { // 这里即使不手动implements Serializable默认Enum已经implements Serializable
    INSTANCE;

    Person person = new Person.PersonBuilder("hehe", 100).build();

    public void doSomething() {}

}
public class Person {

    private final String name; // 必填
private final int age; // 必填 private final int gender; // 可选
private final String tel; // 可选
private final String address; //可选
private final String school; // 可选 public static class PersonBuilder implements Builder<Person> { @Override
public Person build() {
return new Person(this);
} private final String name; // 必填
private final int age; // 必填 private int gender = 0; // 可选
private String tel = ""; // 可选
private String address = ""; //可选
private String school = ""; // 可选 public PersonBuilder(String name, int age) {
this.name = name;
this.age = age;
} public PersonBuilder gender(int gender) {
this.gender = gender;
return this;
} public PersonBuilder tel(String tel) {
this.tel = tel;
return this;
} public PersonBuilder address(String address) {
this.address = address;
return this;
} public PersonBuilder school(String school) {
this.school = school;
return this;
} } private Person(PersonBuilder builder) {
this.name = builder.name;
this.age = builder.age;
this.gender = builder.gender;
this.tel = builder.tel;
this.address = builder.address;
this.school = builder.school;
} @Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
} }
public class Main {

    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
Singleton s = Singleton.INSTANCE;
System.out.println(s.person); ObjectOutputStream bos = new ObjectOutputStream(new FileOutputStream("INS"));
bos.writeObject(s); ObjectInputStream bis = new ObjectInputStream(new FileInputStream("INS"));
Singleton ns = (Singleton) bis.readObject();
System.out.println(ns.person); System.out.println(s == ns ? "Singleton同一实例" : "Singleton非单例");
System.out.println(s.person == ns.person ? "Person同一实例" : "Person不是同一个实例"); } }
运行后打印输出:

Person{name='hehe', age=100}
Person{name='hehe', age=100}
Singleton同一实例
Person同一实例

通过上面测试可以证明使用枚举实现的单例在面对序列化时也是可靠的。

枚举类型的序列化和反序列化并不是真的序列化操作,在序列化枚举类型时仅仅是序列化枚举的name,反序列化也仅仅是通过name找到对应的枚举并返回,所以这样就保证了序列化前后都是同一个对象。

用私有构造器或枚举类型强化Singleton的更多相关文章

  1. 《Effective Java》-——用私有构造器或者枚举类型强化Singleton属性

    Singleton指仅仅被实例化一次的类.Singleton通常被用来代表那些本质上唯一的系统组件,比如窗口管理器或者文件系统.使类成为Singleton会使它的客户端测试变得十分困难,因为无法给Si ...

  2. 《effective java》读书札记第三条用私有构造器或者枚举类型强化Singleton属性

    Singleton指只被实例化一次的类.一般用来搞那些创建很耗资源或者要求系统中只能有一个实例的类. 这个很经常使用.记得曾经实习面试的时候就有这个面试题. 一般採用的方法是将构造器私有化,然后提供一 ...

  3. Effective Java 之 --- 用私有构造器或者枚举类型强化Singleton属性

    Singleton指仅仅被实例化一次的类,通常用来代表那些本质上唯一的系统组件,实现Singleton有三种方法: 1)公有静态成员是个final域,享有特权的用户可以调用AccessibleObje ...

  4. 【读书笔记 - Effective Java】03. 用私有构造器或者枚举类型强化Singleton属性

    实现Singleton(代表本质上唯一的系统组件)的三种方法: 1. 保持私有构造器,导出公有的静态成员,客户端访问该类的唯一实例. 2. 保持私有构造器,公有的成员是静态工厂方法. 3. 单元素的枚 ...

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

      Singleton指仅仅被实例化一次的类 [Gamma95].Singleton通常代表无状态的对象,例如函数(第24项)或者本质上唯一的系统组件.使类称为Singleton会使它的客户端测试变得 ...

  6. 用私有构造器或者枚举类型强化Singleton属性

    1.Singleton指仅仅被实例化一次的类.Singleton通常被用来代表那些本质上唯一的系统组件,如窗口管理器或者文件系统.使类称为Singleton会使它的客户端调试变的十分困难,因为无法给S ...

  7. 创建和销毁对象——用私有构造器或者枚举类型强化Singleton属性

    参考资料:<Effective Java>.<Java核心技术 卷1>.https://www.cnblogs.com/zhaosq/p/10135362.html 基础回顾 ...

  8. 用私有构造器或者枚举类型强化SingleTon(单例)属性

    单例(singleton)就是一个只实例化一次的类.使类成为单例可能会使它的测试变得困难,因为除非它实现了作为其类型的接口,否则不可能用模拟实现来代替这个单例.下面是几种实现单例的方法: 1.共有静态 ...

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

    Singleton是指仅仅被实例化一次的类.通过被用来代表那些本质上唯一的系统组件,比如窗口管理器或者文件系统. 在http://www.cnblogs.com/13jhzeng/p/5256424. ...

随机推荐

  1. python实现定时发送消息

    #!/usr/bin/env python#-*- coding:utf-8 -*- @Author : wujf @Time:2018/8/21 15:59# 自动发送消息from threadin ...

  2. 2、深入学习基本结构——CNN

    这节课主要简单复习一下CNN 从图中例子,1.3共享参数,2.4共享. 要看明白以上参数. 后面就是举例了. 比如声音信号 下面是zero padding 下面是pooling 还可以有mass po ...

  3. ASP.NET CORE--WIN10上无法单步调试解决方法

    参考这篇文章 http://www.cnblogs.com/artech/p/debug-in-vs-code.html In order to be able to debug cross-plat ...

  4. C#中的Attribute定义及用法

    1.Attribute定义 公共语言运行时允许添加类似关键字的描述声明,叫做attributes, 它对程序中的元素进行标注,如类型.字段.方法和属性等.Attributes和Microsoft .N ...

  5. hibernate 管理 Session(单独使用session,非spring)

    Hibernate 自身提供了三种管理 Session 对象的方法 Session 对象的生命周期与本地线程绑定 Session 对象的生命周期与 JTA 事务绑定 Hibernate 托付程序管理 ...

  6. Android - 加入Android的OpenCV依赖库(Android Dependencies) 问题

    加入Android的OpenCV依赖库(Android Dependencies) 问题 本文地址: http://blog.csdn.net/caroline_wendy 假设想要加入OpenCV的 ...

  7. 《学习opencv》笔记——基本数据结构,CvMat,矩阵訪问

        老板让让做一个东东.输入端要用到opencv顺便就来学习一下.买了本书<学习opencv>翻来一看,opencv1.0,去官网上一看.opencv2.49,瞬间有种蛋碎的赶脚.看着 ...

  8. Qt容器类的对象模型及应用(线性结构篇:对于QList来说,sharable默认是false的,但对于接下来讲的QVector来说,sharable默认是true)

    用Qt做过项目开发的人,肯定使用过诸如QList.QVector.QLinkList这样的模板容器类,它们虽然名字长的不同,但使用方法都大致相同, 因为其使用方法都大体相同,很多人可能随便拿一个容器类 ...

  9. angularjs1-3,$apply,$watch

    <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...

  10. android 给url添加cookie

    前些天因为项目需要写了一个通过网络连接去服务端拿数据的方法,但是需要让程序添加上cookie,因为之前对cookie 没有怎么研究过(包括做web 那会也没有用过或者说很少用),所以 一时用起来不太会 ...