用私有构造器或枚举类型强化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. ...
随机推荐
- 【udacity】机器学习-2模型验证
Evernote Export 1.模型的评估与验证简介 机器学习通常是大量传入数据,然后会有一些关于数据的决策.想法和摘要. 2.模型评估 评估模型使用的是各种数据分析的方法,至少需要使用pytho ...
- C++继承与组合
转自https://blog.csdn.net/caoyan_12727/article/details/52337297 类的组合和继承一样,是软件重用的重要方式.组合和继承都是有效地利用已有类的资 ...
- with as递归调用
一.递归调用--在代码中偶尔看到以记之,便于下次学习 https://blog.csdn.net/johnf_nash/article/details/78681060 --查询节点及其下所有子节点 ...
- 数据分析例子-------CTR1
1.CTR: (1)几个概念: impression(展示):用户看到该广告的次数.也就是一个广告被显示了多少次,它就计数多少.比如:打开网站的一个页面,网站上的所有广告就被显示了一次,每个广告增加1 ...
- Selenium 安装与配置及webdriver的API与定位元素
1. selenium安装命令行 C:\Users\wu>cd /d E:\soft\python3.6\Scripts E:\soft\python3.6\Scripts>pip3 in ...
- java+selenium自动化遇到confirm弹窗,出现NoAlertPresentException: no alert open
//操作js的confirm弹窗,bool控制是否点击确定,true为点击确定,false为点击取消 public static void OperaterJSOfConfirm(WebDriver ...
- HDU 2857 Mirror and Light
/* hdu 2857 Mirror and Light 计算几何 镜面反射 */ #include<stdio.h> #include<string.h> #include& ...
- Windows-命令窗口-强制关机命令
Windows +R CMD 命令行窗口shutdown -s -f -t 以上参数中的-s代表关机,-f表示强制关闭所有应用程序,-t 00代表不用等待立即执行(时间以秒计算,把时间改长就变成了定 ...
- 香蕉派 Banana pi BPI-M2 四核开源单板计算机.
Banana PI BPI-M2 是一款四核高性能单板计算机,Banana PI BPI-M2是一款比树莓派更强悍的四核Android4.4产品. Banana PI BPI-M2兼容性强大,能 ...
- A server is already running. Check tmp/pids/server.pid.
A server is already running. Check tmp/pids/server.pid. 把server.pid删除: 学习了: http://stackoverflow.co ...