用私有构造器或枚举类型强化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 笔记(三) 目录结构
理解CentOS 目录结构 首次登录进入,应该是进入了,run文件夹 通过 cd ../ 进入了,最根节点 通过 ls 显示全部文件夹 通过 pwd 查看当前目录 参考: https://www ...
- 【python正则】工作中常用的python正则代码
工作中常用的一些正则代码: 01.用户名正则 import re # 4到16位(字母,数字,下划线,减号)if re.match(r'^[a-zA-Z0-9_-]{4,16}$', "ab ...
- jQuery点击图片放大显示原图效果
HTML部分:<div id="append_parent"></div> JS代码部分: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 ...
- 02023_Arrays类的方法练习
1.定义一个方法,接收一个数组,数组中存储10个学生考试分数,该方法要求返回考试分数最低的后三名考试分数. public static int[] method(double[] arr){ Arra ...
- 2016 年 Java 工具和技术的调查:IDEA 已超过
最近「技术最前线」看到 RebelLabs 做了一次 2016 年 Java 工具与技术的调查,调查报告虽然是 6 月公布的,但数据一点也不过时. 所以「技术最前线」忙会了一中午,写了这篇文章,带大家 ...
- 【我所认知的BIOS】—> uEFI AHCI Driver(6) AtaAtapiPassThruSupported的局部变量们
[我所认知的BIOS]-> uEFI AHCI Driver(6) - AtaAtapiPassThruSupported的局部变量们 LightSeed 5/7/2014 前面5个篇文章把EF ...
- 【iOS开发-78】用代码实现UITabBarController+UINavigationController
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2Vpc3ViYW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA ...
- nor flash 和nand flash 傻傻分不清楚
nor flash和nand flash差别 学习嵌入式有一段时间了,刚接触nor和nand时非常是迷惑.非要逼我写一篇博客才干记清楚. 首先他们都是存储设备.统称叫做flash memory.导致他 ...
- 使用Android Studo开发NDK之Gradle的配置(能debug C代码)
配置: 用的版本号是AS1.5(也能够尝试更高版本号). Gradle地址是distributionUrl=https\://services.gradle.org/distributions/gra ...
- cocos2dx --- 在游戏中显示HTML页面
前文介绍了简单的富文本组件RichText,如今我们来了解下由freeType库做出来的第三方组件.可以直接显示html页面,而且可以实现超链接.和触摸事情. 步骤: 1.在github中 下载 ...