`

反射、枚举与单例

阅读更多
通常我们所使用的单例模式,我们都可以使用反射使它不再单例,如下饿汉式的单例模式:
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的乒乓狂魔
分享到:
评论

相关推荐

    xml、单例模式、反射、枚举

    xml、单例模式、反射、枚举.

    实验12 单例模式与枚举.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    一个单例模式的晋级过程(饿汉-懒汉-DCL-IoDH-枚举)

    一个单例模式的晋级过程(饿汉-懒汉-DCL-IoDH-枚举) 文章目录一个单例模式的晋级过程(饿汉-懒汉-DCL-IoDH-枚举)什么是单例?单例有哪些运用场景?实现1.饿汉式测试优化-final2.懒汉式优化-加锁同步3.DCL双检锁/双重...

    java反射机制.md

    深入了解java反射机制的原理,通过反射机制可以破坏单例模式,如何防止通过反射机制拿到单例模式的构造器呢?用枚举类可破

    enum:一个简单PHP库来添加对枚举的支持

    与myclabs / php-enum相比,这需要更多的工作,但是不需要反射。 它还迫使枚举成为单例。安装composer require tebru/enum用法要使用,扩展AbstractEnum并实现getConstants()方法。 class DirectionEnum extends ...

    Java实验2 反射.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    实验9 Java输入输出流.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    实验6 继承与接口.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    实验7 成员访问控制与异常.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    实验1 Java环境搭建.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    实验9 Servlet.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    实验11 XML解析.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    Java实验7 序列化.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    实验10 JSP.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    Java实验8 数据库.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    Java实验6多线程.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    实验5 网络编程.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    Java实验3泛型.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    Java实验4 注解.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    Java实验1代码.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

Global site tag (gtag.js) - Google Analytics