用私有构造器或枚举类型强化Singleton
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的更多相关文章
- 《Effective Java》-——用私有构造器或者枚举类型强化Singleton属性
Singleton指仅仅被实例化一次的类.Singleton通常被用来代表那些本质上唯一的系统组件,比如窗口管理器或者文件系统.使类成为Singleton会使它的客户端测试变得十分困难,因为无法给Si ...
- 《effective java》读书札记第三条用私有构造器或者枚举类型强化Singleton属性
Singleton指只被实例化一次的类.一般用来搞那些创建很耗资源或者要求系统中只能有一个实例的类. 这个很经常使用.记得曾经实习面试的时候就有这个面试题. 一般採用的方法是将构造器私有化,然后提供一 ...
- Effective Java 之 --- 用私有构造器或者枚举类型强化Singleton属性
Singleton指仅仅被实例化一次的类,通常用来代表那些本质上唯一的系统组件,实现Singleton有三种方法: 1)公有静态成员是个final域,享有特权的用户可以调用AccessibleObje ...
- 【读书笔记 - Effective Java】03. 用私有构造器或者枚举类型强化Singleton属性
实现Singleton(代表本质上唯一的系统组件)的三种方法: 1. 保持私有构造器,导出公有的静态成员,客户端访问该类的唯一实例. 2. 保持私有构造器,公有的成员是静态工厂方法. 3. 单元素的枚 ...
- 第3项:用私有构造器或者枚举类型强化Singleton属性
Singleton指仅仅被实例化一次的类 [Gamma95].Singleton通常代表无状态的对象,例如函数(第24项)或者本质上唯一的系统组件.使类称为Singleton会使它的客户端测试变得 ...
- 用私有构造器或者枚举类型强化Singleton属性
1.Singleton指仅仅被实例化一次的类.Singleton通常被用来代表那些本质上唯一的系统组件,如窗口管理器或者文件系统.使类称为Singleton会使它的客户端调试变的十分困难,因为无法给S ...
- 创建和销毁对象——用私有构造器或者枚举类型强化Singleton属性
参考资料:<Effective Java>.<Java核心技术 卷1>.https://www.cnblogs.com/zhaosq/p/10135362.html 基础回顾 ...
- 用私有构造器或者枚举类型强化SingleTon(单例)属性
单例(singleton)就是一个只实例化一次的类.使类成为单例可能会使它的测试变得困难,因为除非它实现了作为其类型的接口,否则不可能用模拟实现来代替这个单例.下面是几种实现单例的方法: 1.共有静态 ...
- 第3条:用私有构造器或者枚举类型强化Singleton属性
Singleton是指仅仅被实例化一次的类.通过被用来代表那些本质上唯一的系统组件,比如窗口管理器或者文件系统. 在http://www.cnblogs.com/13jhzeng/p/5256424. ...
随机推荐
- CentOS 7 yum 安装redis(更简单)
一.安装redis 1.检查是否有redis yum 源 1 yum install redis 2.下载fedora的epel仓库 1 yum install epel-release 3.安装re ...
- JS防抖与节流
在进行窗口的resize.scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕.此时我们可以采用debounce(防抖)和throttle( ...
- css+div 绘制多边形
/*1.正方形*/ <div id="square"></div> #square { width: 100px; height: 100px; backg ...
- BZOJ 2716/2648 SJY摆棋子 (三维偏序CDQ+树状数组)
题目大意: 洛谷传送门 这明明是一道KD-Tree,CDQ分治是TLE的做法 化简式子,$|x1-x2|-|y1-y2|=(x1+y1)-(x2+y2)$ 而$CDQ$分治只能解决$x1 \leq x ...
- [POJ2104] K – th Number (可持久化线段树 主席树)
题目背景 这是个非常经典的主席树入门题--静态区间第K小 数据已经过加强,请使用主席树.同时请注意常数优化 题目描述 如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值. 输入输 ...
- [poj 2411]Mondriaan's Dream (状压dp)
Mondriaan's Dream Time Limit: 3000MS Memory Limit: 65536K Total Submissions: 18903 Accepted: 10779 D ...
- ftp服务器在linux中安装
1.安装 执行 yum -y install vsftpd 2.检验是否安装vsftpd : rmp -qa | grep vsftpd 默认配置文件/ect/vsftpd/vsftpd.c ...
- Linux 程序包管理-YUM
前端工具YUM管理程序包: rpm管理软件虽然方便,但是需要手工解决软件包的依赖关系:很多时候安装一个软件需要首先安装一个或多个(有时多达上百个)其它软件,手工解决很复杂:使用yum可以解决这个问题 ...
- ExtJs之Ext.XTemplate:模板成员函数
<!DOCTYPE html> <html> <head> <title>ExtJs</title> <meta http-equiv ...
- 交叉编译faac共享库
作者:咕唧咕唧liukun321 来自:http://blog.csdn.net/liukun321 Advanced Audio Coding.一种专为声音数据设计的文件压缩格式,与Mp3不同,它採 ...