一:设计模式
- 概念:设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
- 目的:为了代码可重用性,让代码更容易被他人理解、保证代码可靠性。设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
二:设计原则
设计模式就是因为实现了设计原则(面向对象),从而达到了代码复用,增加可维护性的目的。
- 开闭原则(Open Close Principle)
对扩展开发,对修改关闭
在程序需要进行扩展的时候,不能去修改原有的代码 - 里氏替换原则(Liskov Substitution Principle)
任何基类可以出现的地方,子类一定可以出现
里氏替换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化。而基类于子类的继承关系就是抽象化的具体实现。
- 依赖倒置原则(Dependence Inversion Principle)
面向接口编程,依赖于抽象/接口,而不依赖于具体实现
这个是开闭原则的基础
- 接口隔离原则(Interface Segregation Principle)
使用多个接口比使用单一接口要好,降低代码的耦合度 - 迪米特法则(最少知道原则)(Demeter Principle)
一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。 - 单一职责原则(Single Responsibility Principle)
一个类只做一件事
三:设计模式的分类
总体来说设计模式分为三大类
- 创建型模式,共五种:单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
- 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为模式,共十一钟:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问模式、中介者模式、解释器模式。
本章重点-----创建型模式之单例模式
1. 定义:
单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
类图:
2. 示例
- 首先我们创建一个User类
public class User {
}
- 然后创建一个测试
@org.junit.Test
public void userTest(){
User one = new User();
User two = new User();
System.out.println(one);
System.out.println(two);
System.out.println(one==two);
}
-----------------Result-------------
com.ycj.design.create.singleton.User@c81cdd1
com.ycj.design.create.singleton.User@1fc2b765
false
从上面的代码可以看出两次使用构造函数得到实例的哈希值不一样,实例也就不一样
根据单例模式的定义,现在我们要让两个实例相等(获取同一个实例),那么需要对代码进行修改
public class User {
private User(){}
private static final User INSTANCE = new User();
public static User getInstance(){
return INSTANCE;
}
}
再次测试:
@org.junit.Test
public void userTest(){
User one = User.getInstance();
User two = User.getInstance();
System.out.println(one);
System.out.println(two);
System.out.println(one==two);
}
-----------------Result-------------
com.ycj.design.create.singleton.User@c81cdd1
com.ycj.design.create.singleton.User@c81cdd1
true
最总我们得到了我们想要的结果,两个实例的哈希值一样,这就是单例
3.单例模式的应用
1. 优点:
- 由于单例模式在内存中只存在一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时、而且创建或销毁时性能又无法优化,单例模式地优势就非常明显。
- 由于单例模式只生成一个实例,所以减少了系统地性能开销,当一个对象地产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存地方式来解决(如:spring的bean创建)
- 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
2.缺点
- 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
- 单例模式对测试是不利的。在并行开发环境种,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
- 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑而不关心它是否是单例的,单例模式把“要单例”和业务逻辑融合在一个类里面
3. 使用场景
在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“不良反应”,可以采取单例模式,具体的场景如下:
- 要求生成唯一序列号的环境
- 在整个项目中需要一个共享访问点或共享数据,例如一个web页面上的计数器,可以不用每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是下线程安全的
- 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源;
- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式、
4.注意
单例模式在多线程的应用场合下必须小心使用,如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁
使用单例模式需要注意一点的就是JVM的垃圾回收机制,如果我们的一个单例对象在内存中长久不使用,JVM就认为这个对象就是一个垃圾,在CPU资源空闲的情况下该兑现会被清理掉,下次再调用时就需要重新产生一个对象。如果我们在应用中使用单例类作为有状态值(如计数器)的管理,则会出现恢复原状的情况,应用就会出现故障。如果确实需要采用单例模式来记录有状态的值,则有两个办法可以解决:
- 由容器管理单例的生命周期
JavaEE容器或者框架级容器(如Spring)可以让对象长久驻留内存。当然,自行通过管理对象的生命周期也是一个可行的办法。 - 状态随时记录
可以使用异步记录的方式,或者使用观察者模式。记录状态的变化,写入文件或写入数据库中,确保即使单例对象重新初始化也可以从资源环境获得销毁前的数据,避免应用数据丢失。
4.单例模式的扩展
单例模式实现主要是通过以下两个步骤
- 将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;
- 在该类提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。
单例模式的几种写法
①饿汉式
- 静态常量【可用】
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
- 静态代码块【可用】
public class Singleton {
private static Singleton instance;
static {
instance = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
上面的两种写法都是在类装载的时候就完成了实例化。避免了线程同步问题。
但是,在类装载的时候就完成实例化,没有达到懒加载的效果,如果从始至终都没有使用过这个实例,则会造成内存的浪费
②懒汉式
- 线程不安全【不可用】
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
- 线程不安全,同步代码块【不可用】
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
这两个懒汉式虽然起到了懒加载的效果,但是只能在单线程下使用,如果在多线程下,一个线程进入了if(singleton==null)判断语句块,还未来得及往下执行,另一个线程通过这个判断语句,这时便会产生多个实例。
我们可以测试一下
@org.junit.Test
public void userTest(){
Thread thread1 = new Thread(()->{
Singleton one = Singleton.getInstance();
System.out.println(one);
});
Thread thread2 = new Thread(()->{
Singleton two = Singleton.getInstance();
System.out.println(two);
});
thread1.start();
thread2.start();
}
----------------Result-------------
com.ycj.design.create.singleton.Singleton@42d91a7f
com.ycj.design.create.singleton.Singleton@703b43db
从上面的结果可以判断出两次得到的不是同一个实例(如果结果一样,请多run几次,因为线程运行顺序不可控),所以在多线程中不推荐使用
- 线程安全,同步方法【不推荐使用】
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
这种方式解决了线程安全的问题,但是效率太低了,每个线程在想获取类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就行了。
- 双重检查【不推荐使用】
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {//1
if (singleton == null) {//2
singleton = new Singleton();//3
}
}
}
return singleton;
}
}
如代码所示,我们进行了两次if(singleton == null)检查,这样就可以保证线程安全了。这样实例化代码只用执行一次,后面再次访问时,判断if(singleton==null),直接return实例化对象。
优点:线程安全;延迟加载;效率较高
我们可以跟上一个方法作比较
@org.junit.Test
public void test3(){
Instant start = Instant.now();
//懒汉式,同步方法
for (int i = 0; i < 1000000; i++) {
SingletonPatternFour instance = SingletonPatternFour.getInstance();
}
Instant middle = Instant.now();
//懒汉式,双重检查
for (int i = 0; i < 1000000; i++) {
SingletonPatternSex instance = SingletonPatternSex.getInstance();
}
Instant end = Instant.now();
System.out.println("懒汉式,同步方法:"+Duration.between(start,middle).toNanos());
System.out.println("懒汉式,双重检查:"+Duration.between(middle,end).toNanos());
}
---------------------------------Result-----------------
懒汉式,同步方法:21823700
懒汉式,双重检查:2975900
从上面的结果中可以看出双重检查,效率提高了很多
补充DCL(在java1.5之前中失败了,因为java平台内存模型允许所谓的“无序写入”,即使是加了volatile也没有用)
无序写入
1.线程1进入getInstace()方法。
2.由于instace为空,线程1在//1处进入synchronized块。
3.线程1前进到//3处,但在构造函数执行之前,使实例成为了非空。
4.线程1被线程2预占
5.线程2检查实例是否为空,因为实例不为空,线程2将instance引用返回给一个构造完成但部分初始化了的singleton对象
6.线程2被线程1预占
7线程1通过运行singleton对象的构造函数并将引用反回给他,来完成对该类的初始化。
③静态内部类【推荐用】
public class Singleton {
private Singleton() {}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。
不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有懒加载的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,变得线程是无法进入的。
优点:避免了线程不安全,延迟加载,效率高
④ 枚举【强烈推荐用】
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反射创建新的对象和反序列化重新创建新的对象。
我们可来测试一下
- 多线程
@org.junit.Test
public void test1(){
Thread thread = new Thread(() -> {
SingletonPatternAge instance = SingletonPatternAge.INSTANCE;
System.out.println(instance.hashCode());
});
Thread thread1 = new Thread(() -> {
SingletonPatternAge instance = SingletonPatternAge.INSTANCE;
System.out.println(instance.hashCode());
});
thread.start();
thread1.start();
}
------------------------Result----------------
2121564079
2121564079
从上面的代码可以看出在多线程下也是同一个实例
- 反射
首先用一般的单例模式来进行反射
@org.junit.Test
public void test() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Singleton one = Singleton.getInstance();
Singleton two = Singleton.getInstance();
//反射攻击
//得到Singleton的无参构造函数
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);//设置可万一访问
Singleton singleton = constructor.newInstance();//创建一个Singleton实例
System.out.println("---------正常情况---------");
System.out.println(one);
System.out.println(two);
System.out.println("---------反射结果---------");
System.out.println(singleton);
}
---------------------Result-----------------------------
---------正常情况---------
com.ycj.design.create.singleton.Singleton@c81cdd1
com.ycj.design.create.singleton.Singleton@c81cdd1
---------反射结果---------
com.ycj.design.create.singleton.Singleton@1fc2b765
从上面的代码中我们用反射创建一个新的对象(反射可以调用私有构造器),在结果里我们得出反射处的对象不再是同一个对象,所以破环了单例模式,当然也有解决方案: 《effective java》中提了几句话:“享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要低于这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。”
但是这样做比较麻烦,
public class Singleton implements Serializable {
private static final long serialVersionUID = 8650393098362241962L;
private static Singleton instance;
private Singleton(){
if (null != instance) {
throw new RuntimeException("单例模式被侵犯!");
}
}
public static Singleton getInstance(){
if(instance==null)instance=new Singleton();
return instance;
}
}
现在用枚举,更加安全,简洁
我们用枚举来反射试试
@org.junit.Test
public void test() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
SingletonPatternAge one = SingletonPatternAge.INSTANCE;
SingletonPatternAge two = SingletonPatternAge.INSTANCE;
System.out.println("---------正常情况---------");
System.out.println(one.hashCode());
System.out.println(two.hashCode());
//反射攻击
//得到Singleton的无参构造函数
Constructor<SingletonPatternAge> constructor = SingletonPatternAge.class.getDeclaredConstructor();
constructor.setAccessible(true);//设置可万一访问
SingletonPatternAge singleton = constructor.newInstance();//创建一个Singleton实例
System.out.println("---------反射结果---------");
System.out.println(singleton);
}
--------------------Result--------------------------
---------正常情况---------
209833425
209833425
java.lang.NoSuchMethodException: com.ycj.design.create.singleton.SingletonPatternAge.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3350)
at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2554)
at com.ycj.design.create.singleton.Test.test(Test.java:54)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
从上面的结果中可以看出在,反射的时候,获取私有构造器出错了
为什么会出错了?
我们先看一下Enum源码,发现所有的枚举类都是Enum的子类,Enum没有无参构造函数,只有一个参数为(String name, int ordinal)的构造器,所以获取不到无参构造函数。
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
private final String name;
public final String name() {
return name;
}
private final int ordinal;
public final int ordinal() {
return ordinal;
}
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
//余下省略
枚举Enum是个抽象类,其实一旦一个类声明为枚举,实际上就是继承了Enum,所以会有(String name, int ordinal)的构造器。这时,你可能会想,用这个构造器是不是就能够反射攻击,那么现在来试试
@org.junit.Test
public void test() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
SingletonPatternAge one = SingletonPatternAge.INSTANCE;
SingletonPatternAge two = SingletonPatternAge.INSTANCE;
System.out.println("---------正常情况---------");
System.out.println(one.hashCode());
System.out.println(two.hashCode());
//反射攻击
//得到Singleton的无参构造函数
Constructor<SingletonPatternAge> constructor = SingletonPatternAge.class.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);//设置可万一访问
SingletonPatternAge singleton = constructor.newInstance();//创建一个Singleton实例
System.out.println("---------反射结果---------");
System.out.println(singleton);
}
---------------Result------------------------
---------正常情况---------
209833425
209833425
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:484)
at com.ycj.design.create.singleton.Test.test(Test.java:56)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
然后发现还是保错了,说不能通过反射创建枚举对象
接下来我们看一下Constructor的newInstance方法的源码:
@CallerSensitive
@ForceInline // to ensure Reflection.getCallerClass optimization
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, clazz, modifiers);
}
if (clazz.getModifiers() & Modifier.ENUM) != 0)//就是这里,会检查该类是否是Enum修饰
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
我们会发现,反射在通过newInstance创建对象时,会检查该类是否时枚举类,如果是就抛出异常,不允许创建对象
所以枚举单例,能够防止反射攻击
- 序列化
还是一样我们首先用非枚举的单例来实验
public class Singleton implements Serializable {
private static final long serialVersionUID = 8650393098362241962L;
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null)instance=new Singleton();
return instance;
}
}
@org.junit.Test
public void userTest() throws IOException, ClassNotFoundException {
Singleton one = Singleton.getInstance();
Singleton two = Singleton.getInstance();
System.out.println("---------正常情况---------");
System.out.println(one);
System.out.println(two);
//序列化
//输出到一个文本中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.txt"));
oos.writeObject(one);
oos.flush();
oos.close();
//从一个文本中获取对象
FileInputStream fis = new FileInputStream("Singleton.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Singleton readObject = (Singleton) ois.readObject();//.readObject只会返回一个新的实例
ois.close();
System.out.println("---------序列化---------");
System.out.println(readObject);
}
---------------Result------------------------
---------正常情况---------
com.ycj.design.create.singleton.Singleton@c81cdd1
com.ycj.design.create.singleton.Singleton@c81cdd1
---------序列化后---------
com.ycj.design.create.singleton.Singleton@704d6e83
从上面的结果中,我们可以看出通过反序列化获取的对象不在是同一个对象,
那么非枚举单例,是怎么解决这个反序列化的呢?
就是重写readResolve()方法
public class Singleton implements Serializable {
private static final long serialVersionUID = 8650393098362241962L;
private static Singleton instance;
private Singleton(){
if (null != instance) {
throw new RuntimeException("单例模式被侵犯!");
}
}
public static Singleton getInstance(){
if(instance==null)instance=new Singleton();
return instance;
}
// 防止反序列化获取多个对象的漏洞。
// 无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。
// 实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象。
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
readResolve方法会在反序列化的时候自动调用,它的结果为返回值,并且无视掉反序列化的值,即使那个字节码已经被解析。
然后我们继续用上面的测试方法进行测试
---------正常情况---------
com.ycj.design.create.singleton.Singleton@c81cdd1
com.ycj.design.create.singleton.Singleton@c81cdd1
---------序列化---------
com.ycj.design.create.singleton.Singleton@c81cdd1
可以看到结果一样,
接下来,我们用枚举单例来试试
@org.junit.Test
public void userTest() throws IOException, ClassNotFoundException {
SingletonPatternAge one = SingletonPatternAge.INSTANCE;
SingletonPatternAge two = SingletonPatternAge.INSTANCE;
System.out.println("---------正常情况---------");
System.out.println(one.hashCode());
System.out.println(two.hashCode());
//序列化
//输出到一个文本中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.txt"));
oos.writeObject(one);
oos.flush();
oos.close();
//从一个文本中获取对象
FileInputStream fis = new FileInputStream("Singleton.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
SingletonPatternAge readObject = (SingletonPatternAge) ois.readObject();//.readObject只会返回一个新的实例
ois.close();
System.out.println("---------反序列化---------");
System.out.println(readObject.hashCode());
}
--------------------Result------------------------
---------正常情况---------
209833425
209833425
---------反序列化---------
209833425
可以看到枚举成功方防止了反序列化的问题
在枚举类型的序列化和反序列化上,Java做了特殊的规定:
在序列化的是侯,Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候,则是通过java.lang.Enum的valueOf方法来根据name查找对象。同时,编译器是不允许任何对这种序列化机制的进行定制,因此禁用了wiriteObject,readObject,readObjectNoData,writeReplace和readResolve等方法,
下面是valueOf方法的源码
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
从代码中可以看到,代码会尝试从调用enumType这个对象的enumConstantDirectory方法返回的map中获取名字为name的枚举对象,
如果不存在就会抛出异常。
再进一步跟到enumConstantDirectory方法,就会发现到最后会以反射的方式调用enumType这个类型的values静态方法,
然后用返回结果填充enumType这个Class对象中的enumConstantDirectory属性
enumConstantDirectory源码
Map<String, T> enumConstantDirectory() {
Map<String, T> directory = enumConstantDirectory;
if (directory == null) {
T[] universe = getEnumConstantsShared();
if (universe == null)
throw new IllegalArgumentException(
getName() + " is not an enum type");
directory = new HashMap<>((int)(universe.length / 0.75f) + 1);
for (T constant : universe) {
directory.put(((Enum<?>)constant).name(), constant);
}
enumConstantDirectory = directory;
}
return directory;
}
getEnumConstantsShared源码
T[] getEnumConstantsShared() {
T[] constants = enumConstants;
if (constants == null) {
if (!isEnum()) return null;
try {
final Method values = getMethod("values");
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<>() {
public Void run() {
values.setAccessible(true);
return null;
}
});
@SuppressWarnings("unchecked")
T[] temporaryConstants = (T[])values.invoke(null);
enumConstants = constants = temporaryConstants;
}
// These can happen when users concoct enum-like classes
// that don't comply with the enum spec.
catch (InvocationTargetException | NoSuchMethodException |
IllegalAccessException ex) { return null; }
}
return constants;
}
总结
从上面的例子解析中,我们可以得出使用单例模式最好的方法就是枚举单例,简洁安全。