反射、枚举与单例

简介:
通常我们所使用的单例模式,我们都可以使用反射使它不再单例,如下饿汉式的单例模式:  
?
1
2
3
4
5
6
7
8
9
10
public final class Singleton {
 
     private static final Singleton instance= new Singleton();
     
     private Singleton(){}
     
     public static Singleton getInstance(){
         return instance;
     }
}

测试案例如下:  
?
1
2
3
4
5
6
7
8
9
10
11
12
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和上述两者应该不是同一个对象,测试结果如下:  
?
1
2
3
4
5
com.lg.design.singleton.hungry.Singleton @15e3d24a
com.lg.design.singleton.hungry.Singleton @15e3d24a
com.lg.design.singleton.hungry.Singleton @20030380
true
false

所以说通常我们所使用的单例模式,我们都可以使用反射使它不再单例。然而单例使用枚举的话,却可以避免被反射。 
单例如下:
 
?
1
2
3
4
5
6
public enum Singleton {
     
     instance;
     private Singleton(){}
     
}

反射如下:  
?
1
2
3
4
5
6
7
8
9
10
11
12
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);

然后就报错:  
?
1
2
3
4
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两个属性,枚举的源码如下:  
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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的构造器,那我们就使用该构造器看能不能创建出对象:
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);

然后也报错:  
?
1
2
3
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)方法时抛出异常,说不能够反射枚举,具体源码如下:  
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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创建对象时,会检查该类是否是枚举类,如果是,则抛出异常,反射失败。 
也就是说使用枚举可以避免被反射,从而可以达到单例的效果。 
相关文章
|
6月前
|
开发框架 Java 编译器
Java反射,枚举讲解
Java反射,枚举讲解
71 0
|
设计模式 Java
Java反射(Class、反射实例化、反射与单例、获取类结构)附带相关面试题
1.了解反射,2.Class类的三种实例化方法,3.反射机制与对象实例化,4.反射与单例设计模式,5.通过反射获取类结构的信息
293 0
|
6月前
|
Java
【反射】Java反射机制 -- 常用构造器与方法
【反射】Java反射机制 -- 常用构造器与方法
60 0
|
6月前
|
XML 安全 Java
反射&&枚举
反射&&枚举
36 0
|
SQL 缓存 安全
Java枚举单例模式比DCL和静态单例要好?———反编译分析单例枚举类
枚举单例模式比DCL和静态单例模式要好?为什么好呢?本文带你一探究竟!
115 0
Java枚举单例模式比DCL和静态单例要好?———反编译分析单例枚举类
|
Java API
Java反射(通过反射获取构造函数、方法、属性)
1.通过反射获取构造函数,2.通过反射获取方法,3.通过反射调用成员属性
114 0
|
安全 Java 编译器
反射与枚举
本篇文章主要介绍Java语法中的反射与枚举部分。
111 0
反射与枚举
|
安全 Java 编译器
枚举使用、转数组、实现接口、枚举单例
枚举使用、转数组、实现接口、枚举单例
123 0
|
Java 程序员 API
枚举,注解 ,反射
枚举,注解 ,反射
82 0
|
安全 Java 编译器
今天我们来聊聊单例模式和枚举
今天我们来聊聊单例模式和枚举
今天我们来聊聊单例模式和枚举