用私有构造器或枚举类型强化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. ...
随机推荐
- eclipse中的maven项目部署到tomcat中
http://www.cnblogs.com/guodefu909/p/4874549.html
- linux下查看mysql版本的四种方法
Linux查看MySQL版本的四种方法 1 在终端下执行 mysql -V 2 在help中查找 mysql --help |grep Distrib 3 在mysql 里查看 select vers ...
- python笔记4----字典
1.哈希: 输入任意长度,输出固定长度. 即判断是否哈希,即判断可不可变. 2.创建字典 (1)直接创建:dic={1:'a',2:'b',3:'c'} (2)dict函数创建: list=[(1,' ...
- Django_学生管理系统
一. Django简易学生管理系统 1.在pycharm中创建工程student_manage_system,添加app:student_manage 2.配置静态文件:在工程项目目录下新建目录sta ...
- [USACO4.2] 草地排水 Drainage Ditches (最大流)
题目背景 在农夫约翰的农场上,每逢下雨,贝茜最喜欢的三叶草地就积聚了一潭水.这意味着草地被水淹没了,并且小草要继续生长还要花相当长一段时间.因此,农夫约翰修建了一套排水系统来使贝茜的草地免除被大水淹没 ...
- [Papers] Semantic Segmentation Papers(1)
目录 FCN Abstract Introduction Related Work FCN Adapting classifiers for dense prediction Shift-and-st ...
- 关于高校表白APP的用户模板和用户场景
用户模板一: 用户名 小明 性别,年龄 男,20岁 用户状况 单身,在校大学生 生活爱好 喜欢打篮球,唱歌 典型场景 希望找到一个心仪的可以走到最后的姑娘 典型描述 交友 用户比例 ? 用户场景一 ...
- Java 内部类机制
内部类(inner class)是定义在另一个类中的类.为什么需要使用内部类呢?其主要原因有以下三点: 1.内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据. 2.内部类可以对同一个包中 ...
- C# string bytes互转
string str = "spike"; byte[] bytes = System.Text.Encoding.Default.GetBytes(str); foreach ( ...
- 通过setSystemUiVisibility实现状态栏跟Activity之间的位置关系
曾经说到去除状态栏和标题栏总会用到动态代码的方式实现: getWindow().setFlags(WindowManager.LayoutParams. FLAG_FULLSCREEN , Windo ...