文档章节

反射、枚举与单例

乒乓狂魔
 乒乓狂魔
发布于 2015/02/07 10:37
字数 852
阅读 154
收藏 0
通常我们所使用的单例模式,我们都可以使用反射使它不再单例,如下饿汉式的单例模式:
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<Singleton> 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@15e3d24a
com.lg.design.singleton.hungry.Singleton@15e3d24a
com.lg.design.singleton.hungry.Singleton@20030380
true
false

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

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

反射如下:
Singleton singleton1=Singleton.instance;
		Singleton singleton2=Singleton.instance;
		
		Constructor<Singleton> 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.<init>()
	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<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;
    }

    public String toString() {
        return name;
    }
 //略
}

枚举Enum是一个抽象类,一个类一旦声明为枚举,其实就是继承了Enum,所以会有(String.class,int.class)的构造器(就是父类Enum的构造器),具体的原理可以根据生成的字节码反编译后得知,可以参考这篇文章http://pf-miles.iteye.com/blog/187155#bc2340028
既然是可以获取到父类Enum的构造器,那我们就使用该构造器看能不能创建出对象:

Singleton singleton1=Singleton.instance;
		Singleton singleton2=Singleton.instance;
		
		Constructor<Singleton> constructor=Singleton.class.getDeclaredConstructor(String.class,int.class);
		//Constructor<Singleton> 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://lgbolgger.iteye.com/blog/2159940
作者:iteye的乒乓狂魔

© 著作权归作者所有

共有 人打赏支持
乒乓狂魔
粉丝 994
博文 105
码字总数 271356
作品 0
长宁
程序员
为什么我墙裂建议大家使用枚举来实现单例。

关于单例模式,我的博客中有很多文章介绍过。作为23种设计模式中最为常用的设计模式,单例模式并没有想象的那么简单。因为在设计单例的时候要考虑很多问题,比如线程安全问题、序列化对单例的...

06/10
0
0
单例设计模式 (2)

1.静态内部类实现 在上一版的时候用的是懒汉和饿汉来做单例模式,如果我们采用静态内部类的话,就可以通过classloader来懒加载单例 用静态内部类实现单例模式: 这里有几个需要注意的点: 1....

蠢廿
2017/12/04
0
0
Java 单例模式 总结整理

分享总结常见的5种单例模式: 第一、单例模式的使用场景 A、Windows的任务管理器、回收站、文件系统如F盘,都是很典型的单例模式 ; B、项目中,读取配置文件的类,一般也是单例模式,没有必...

故新
2017/11/09
0
0
为什么我墙裂建议大家使用枚举来实现单例

我们知道,单例模式,一般有七种写法,那么这七种写法中,最好的是哪一种呢?为什么呢?本文就来抽丝剥茧一下。 哪种写单例的方式最好 在StakcOverflow中,有一个关于What is an efficient ...

冷_6986
06/13
0
0
如何防止单例模式被JAVA反射攻击

单例模式相信大家都知道,用过的人不在少数。之前写过一篇博文《singleton模式四种线程安全的实现》(参见:http://blog.csdn.NET/u013256816/article/details/50427061),讲诉了单例模式的...

vshcxl
2016/11/28
8
0

没有更多内容

加载失败,请刷新页面

加载更多

GO冒泡,二分查找

package mainimport("fmt")func main() {var arr [5]int = [5]int{11,13,9,2,25}maopao(&arr)fmt.Println("arr = ", arr) //[2 9 11 13 25]findIndex := binaryFind(&arr, 0......

汤汤圆圆
9分钟前
1
0
工作2年半跳槽面试阿里,成功拿到offer,凭什么?

2015年刚毕业的我,进入了一家小小的公司实习工作,在学校学了三年软件开发的我,还是想去寻找一份互联网行业的工作,这样更能学以致用发挥自己的特长。一直到18年三月份,我辞掉已有的工作,...

java知识分子
14分钟前
1
0
讲述下:Linux的10个最危险的命令

导读 Linux命令行佷有用、很高效,也很有趣,但有时候也很危险,尤其是在你不确定你自己在正在做什么时候。这篇文章将会向你介绍十条命令,但你最好不要尝试着去使用。 当然,以下命令通常都...

问题终结者
18分钟前
1
0
分库分表后如何部署上线?

引言 我们先来讲一个段子 面试官:“有并发的经验没?” 应聘者:“有一点。” 面试官:“那你们为了处理并发,做了哪些优化?” 应聘者:“前后端分离啊,限流啊,分库分表啊。。” 面试官:...

Java烂猪皮
23分钟前
1
0
Redis源码阅读笔记-快速列表

快速列表 快速列表(quicklist)是由压缩列表(ziplist)组成的一个双向链表,链表中,每一个节点都是以压缩列表(ziplist)的结构保存。 在 Redis3.2 后加入的新数据结构,在列表键中取代了双向链...

Jian_Ming
41分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部