博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
反射、枚举与单例
阅读量:4300 次
发布时间:2019-05-27

本文共 4668 字,大约阅读时间需要 15 分钟。

通常我们所使用的单例模式,我们都可以使用反射使它不再单例,如下饿汉式的单例模式:

public final class Singleton {
private static final Singleton instance=new Singleton(); private Singleton(){ public static Singleton getInstance(){ return instance; }}

测试案例如下:

Singleton singleton1=Singleton.getInstance();        Singleton singleton2=Singleton.getInstance();        Constructor
constructor=Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton singleton3=constructor.newInstance(); System.out.println(singleton1); System.out.println(singleton2); System.out.println(singleton3); System.out.println(singleton1==singleton2); System.out.println(singleton1==singleton3);

其中singleton1、singleton2都是通过我们所实现的单例模式来获取的对象,他们应该是同一个对象,singleton3则是通过反射获取无参构造器,constructor.setAccessible(true)来获取访问权限,最后通过无参构造器来创建一个对象singleton3,singleton3和上述两者应该不是同一个对象,测试结果如下:

com.lg.design.singleton.hungry.Singleton@15e3d24acom.lg.design.singleton.hungry.Singleton@15e3d24acom.lg.design.singleton.hungry.Singleton@20030380truefalse

所以说通常我们所使用的单例模式,我们都可以使用反射使它不再单例。然而单例使用枚举的话,却可以避免被反射。

单例如下:

public enum Singleton {    instance;    private Singleton(){}}

反射如下:

Singleton singleton1=Singleton.instance;        Singleton singleton2=Singleton.instance;        Constructor
constructor=Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton singleton3=constructor.newInstance(); System.out.println(singleton1); System.out.println(singleton2); System.out.println(singleton3); System.out.println(singleton1==singleton2); System.out.println(singleton1==singleton3);

然后就报错:

Exception in thread "main" java.lang.NoSuchMethodException: com.lg.design.singleton.enumsingleton.Singleton.
() at java.lang.Class.getConstructor0(Class.java:2849) at java.lang.Class.getDeclaredConstructor(Class.java:2053) at com.lg.design.singleton.enumsingleton.Test.main(Test.java:14)

没有这个无参构造器,通过调试Singleton.class.getDeclaredConstructors()获取所有构造器,会发现并没有我们所设置的无参构造器,只有一个参数为(String.class,int.class)构造器,然后我们就可以明白了,这里的参数其实就是枚举的名字和所在枚举中位置,即枚举的name和ordinal两个属性,枚举的源码如下:

public abstract class Enum
> implements Comparable
, 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; } public String toString() { return name; } //略}

枚举Enum是一个抽象类,一个类一旦声明为枚举,其实就是继承了Enum,所以会有(String.class,int.class)的构造器(就是父类Enum的构造器),具体的原理可以根据生成的字节码反编译后得知,可以参考这篇文章。

既然是可以获取到父类Enum的构造器,那我们就使用该构造器看能不能创建出对象:

Singleton singleton1=Singleton.instance;        Singleton singleton2=Singleton.instance;        Constructor
constructor=Singleton.class.getDeclaredConstructor(String.class,int.class); //Constructor
constructor=Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton singleton3=constructor.newInstance("otherInstance",9); //Singleton singleton3=constructor.newInstance(); System.out.println(singleton1); System.out.println(singleton2); System.out.println(singleton3); System.out.println(singleton1==singleton2); System.out.println(singleton1==singleton3);

然后也报错:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects    at java.lang.reflect.Constructor.newInstance(Constructor.java:521)    at com.lg.design.singleton.enumsingleton.Test.main(Test.java:16)

之前的错是说没有构造器,这次我们能够拿到构造器了,只是在使用构造器执行newInstance(“otherInstance”,9)方法时抛出异常,说不能够反射枚举,具体源码如下:

public T newInstance(Object ... initargs)        throws InstantiationException, IllegalAccessException,               IllegalArgumentException, InvocationTargetException    {        if (!override) {            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {                Class
caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } }//我们关注的重点,如果类含有ENUM修饰,调用该方法时直接报错 if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } return (T) ca.newInstance(initargs); }

也就是说反射在通过newInstance创建对象时,会检查该类是否是枚举类,如果是,则抛出异常,反射失败。

也就是说使用枚举可以避免被反射,从而可以达到单例的效果。

转载地址:http://qzxws.baihongyu.com/

你可能感兴趣的文章
web.xml 配置中classpath: 与classpath*:的区别
查看>>
suse如何修改ssh端口为2222?
查看>>
详细理解“>/dev/null 2>&1”
查看>>
suse如何创建定时任务?
查看>>
suse搭建ftp服务器方法
查看>>
centos虚拟机设置共享文件夹并通过我的电脑访问[增加smbd端口修改]
查看>>
文件拷贝(IFileOperation::CopyItem)
查看>>
MapReduce的 Speculative Execution机制
查看>>
大数据学习之路------借助HDP SANDBOX开始学习
查看>>
Hadoop基础学习:基于Hortonworks HDP
查看>>
为什么linux安装程序 都要放到/usr/local目录下
查看>>
Hive安装前扫盲之Derby和Metastore
查看>>
永久修改PATH环境变量的几种办法
查看>>
大数据学习之HDP SANDBOX开始学习
查看>>
Hive Beeline使用
查看>>
Centos6安装图形界面(hdp不需要,hdp直接从github上下载数据即可)
查看>>
CentOS7 中把yum源更换成163源
查看>>
关于yum Error: Cannot retrieve repository metadata (repomd.xml) for repository:xxxxxx.
查看>>
linux下载github中的文件
查看>>
HDP Sandbox里面git clone不了数据(HTTP request failed)【目前还没解决,所以hive的练习先暂时搁置了】
查看>>