反射、枚举与单例

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


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创建对象时,会检查该类是否是枚举类,如果是,则抛出异常,反射失败。
也就是说使用枚举可以避免被反射,从而可以达到单例的效果。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值