每天开心一点

Java反射机制的基本概念与使用

2018-01-10 08:38:00    六月    1329    来源: http://blog.csdn.net/mlc1218559742/article/details/52754310

本篇文章分为以下几个部分:

1.认识反射

2.反射的源头(Class类)

3.利用反射操作构造方法

4.利用反射调用类中的方法

5.反射中的invoke方法

6.利用反射调用类中的属性

反射在我们普通程序开发中基本使用不到,但是在我们底层的程序设计中使用特别广泛,例如代理模式、工厂模式等一些设计模式,包括我们使用的开发工具以及各大开源框架底层都使用到了反射的原理。所以掌握了Java的反射机制对我们理解各大开源框架都有很好的帮助。


1.认识反射

反射,从这个“反”字可以看出与我们平时正常的使用逻辑肯定不一样,那么到底什么地方不一样了?想要了解“反”,就得先了解一下“正”的概念。

在正常情况下,如果要使用一个类,必须要经过以下几个步骤:

(1)使用important导入类所在的包(类: java.lang.Class

(2)通过关键字new进行类对象实例化(构造方法: java.lang.reflect.Constructor

(3)产生对象可以使用“对象.属性”进行类中属性的调用(属性: java.lang.reflect.Field)

(4)通过“对象.方法()”调用类中的方法(方法: java.lang.reflect.Method

括号中的红色字体是每个步骤对应反射中使用到的类,如果现在不了解,可以先不用管,后面会一一介绍,这里是为了方便进行比较。

在反射中,使用一个类并不需要导入类的所在包,只要知道类的完整路径就可以知道该类中的所有信息。

反射不需要有明确的类型对象,所有的对象都 使用Object表示。可以直接用Object的与反射机制的混合调用类中的方法。


2.反射的源头(Class类)

在认识反射机制之前,必须要介绍一下 Class类,Class类是整个反射操作的源头,该类的定义如下:

[java] view plain copy
  1. public  final  class Class<T>  

  2. extends Object  

  3. implements Serializable, GenericDeclaration, Type, AnnotatedElement  

Class类的实例表示正在运行的Java应用程序中的类和接口。

如果要想使用Class类进行操作,就必须首先产生Class类这个对象,一共有三种方法:

(1)Object类中提供了一个返回Class类的方法,定义如下:

[java] view plain copy
  1. public  final Class<?> getClass()  

(2)利用“类.class”取得。

(3)利用Class类的Static方法取得。

[java] view plain copy
  1. public  static Class<?> forName(String className)  

  2.                          throws ClassNotFoundException  

在程序开发过程中,使用第二种方法比较多。但是在程序框架设计中,都是使用第三种方法,也就是反射机制用到的方法。

class类实例化对象:

Class类如果使用forName()方法之后,就可以调用Class类中newInstance()无参构造函数方法进行操作,该方法定义如下:

[java] view plain copy
  1. public T newInstance()  

  2.                throws InstantiationException,  

  3.                      IllegalAccessException  


该方法表示创建此Class对象所表示的类的一个新实例。

具体实例如下:

[java] view plain copy
  1. class Student {  

  2.   

  3.      public Student() {  

  4.         System.out.println( "Student类的构造方法");  

  5.     }  

  6.   

  7.      @Override  

  8.      public String toString() {  

  9.          return  "Student类的toString方法";  

  10.     }  

  11. }  

  12.   

  13. public  class ReflectDemo {  

  14.   

  15.      public  static  void main(String[] args)  throws Exception {  

  16.         Class<?> cls = Class.forName( "com.iflytek.Student");  

  17.          // 相当于关键字实例化对象,Object obj = new Student();  

  18.         Object obj = cls.newInstance();  

  19.         System.out.println(obj);  

  20.     }  

  21.   

  22. }  


输出结果为:

[java] view plain copy
  1. Student类的构造方法  

  2. Student类的toString方法  

通过上面的实例可以看出,调用newInstace()方法时,程序会默认调用Student类的无参构造方法,并且获取到了Student类的实例化对象,可以调用Student类里面的方法属性。


3.利用反射操作构造方法

在Class类中有两个方法可以获取类中的构造方法,分别是:

获取类中所有的构造方法:

[java] view plain copy
  1. public Constructor<?>[] getConstructors()  

  2.                                   throws SecurityException  

获取类中指定的构造方法:

[java] view plain copy
  1. public Constructor<T> getConstructor(Class<?>... parameterTypes)  

  2.                                throws NoSuchMethodException,  

  3.                                      SecurityException  

这两个方法返回的都是反射包(java.lang.reflect.*)中的Constructor类,该类提供了单个构造方法的信息以及对它的访问权限。

下面以获取String类中的所有构造方法为例,代码如下:

[java] view plain copy
  1. public  class ReflectStringDemo {  

  2.      public  static  void main(String[] args)  throws Exception{  

  3.         Class<?> cls = Class.forName( "java.lang.String");  

  4.          //获取所有构造函数  

  5.         Constructor<?>[] cons = cls.getConstructors();  

  6.          //循环打印  

  7.          for ( int i =  0; i < cons.length; i++) {  

  8.             System.out.println(cons[i]);  

  9.         }  

  10.     }  

  11. }  

打印结果:

[java] view plain copy
  1. public java.lang.String( byte[])  

  2. public java.lang.String( byte[], int, int)  

  3. public java.lang.String( byte[],java.nio.charset.Charset)  

  4. public java.lang.String( byte[],java.lang.String)  throws java.io.UnsupportedEncodingException  

  5. public java.lang.String( byte[], int, int,java.nio.charset.Charset)  

  6. public java.lang.String(java.lang.StringBuilder)  

  7. public java.lang.String(java.lang.StringBuffer)  

  8. public java.lang.String( int[], int, int)  

  9. public java.lang.String( char[], int, int)  

  10. public java.lang.String( char[])  

  11. public java.lang.String(java.lang.String)  

  12. public java.lang.String()  

  13. public java.lang.String( byte[], int, int,java.lang.String)  throws java.io.UnsupportedEncodingException  

  14. public java.lang.String( byte[], int)  

  15. public java.lang.String( byte[], int, int, int)  

上面实例获取了String类中的所有构造方法,包括构造方法中的参数、异常等。

获取所有构造方法看上去并不难,如果想要进行指定构造方法的调用,则必须关注Constructor类,使用newInstance()方法进行实例。

[java] view plain copy
  1. public T newInstance(Object... initargs)  

  2.                throws InstantiationException,  

  3.                      IllegalAccessException,  

  4.                      IllegalArgumentException,  

  5.                      InvocationTargetException  

获取指定有参构造方法并且实例化对象实例:

[java] view plain copy
  1. import java.lang.reflect.Constructor;  

  2.   

  3. class Student2 {  

  4.      private String name;  

  5.   

  6.      private Integer age;  

  7.   

  8.      public Student2(String name, Integer age) {  

  9.          this.name = name;  

  10.          this.age = age;  

  11.     }  

  12.   

  13.      @Override  

  14.      public String toString() {  

  15.          return  "name:" +  this.name +  ";age:" +  this.age;  

  16.     }  

  17. }  

  18.   

  19. public  class ReflectConstructorDemo {  

  20.   

  21.      public  static  void main(String[] args)  throws Exception {  

  22.         Class<?> cls = Class.forName( "com.iflytek.Student2");  

  23.         Constructor<?> con = cls.getConstructor(String. class, Integer. class);  

  24.          // 这里就相当于Object obj = new Student2("马小超",20);  

  25.         Object obj = con.newInstance( "马小超"20);  

  26.         System.out.println(obj);  

  27.     }  

  28.   

  29. }  

打印结果:

[java] view plain copy
  1. name:马小超;age: 20  


通过上面可以看出,如果要实例化一个对象,使用无参构造方法比有参构造方法简单的多,使用无参直接调用newInstance()方法,使用有参则先获取有参构造方法,再通过Constructor中的newInstance()方法,并用指定的初始化参数初始化改实例。很多框架中的底层代码默认都是使用无参构造方法来实例化对象,所以在简单Java类开发中都要明确给出无参构造方法。


4.利用反射调用类中的方法
获取类中的方法可以分为两大类,每个大类中又可以分为两小类,风别是:

获取包括父类集成而来的方法:

         获取全部方法:

[java] view plain copy
  1. public Method[] getMethods()  

  2.                      throws SecurityException  

         获取指定方法:

[java] view plain copy
  1. public Method getMethod(String name,Class<?>... parameterTypes)  

  2.                                     throws NoSuchMethodException,  

  3.                                     SecurityException  

获取本类中定义的方法:

         获取全部方法:

[java] view plain copy
  1. public Method[] getDeclaredMethods()  

  2.                              throws SecurityException  

         获取指定方法:

[java] view plain copy
  1. public Method getDeclaredMethod(String name,  

  2.                                 Class<?>... parameterTypes)  

  3.                           throws NoSuchMethodException,  

  4.                                 SecurityException  

实例:

[java] view plain copy
  1. import java.lang.reflect.Method;  

  2.   

  3.   

  4. class Student3{  

  5.      public  void fun(){};  

  6.       

  7.      public  void talk(){};  

  8. }  

  9.   

  10.   

  11. public  class ReflectMethodStuDemo {  

  12.      public  static  void main(String[] args)  throws ClassNotFoundException{  

  13.         Class<?> cls = Class.forName( "com.iflytek.Student3");  

  14.          //获取本类中定义的方法  

  15.         Method[] method = cls.getDeclaredMethods();  

  16.          //循环打印  

  17.          for ( int i =  0; i < method.length; i++) {  

  18.             System.out.println(method[i]);  

  19.         }  

  20.     }  

  21. }  


打印结果:

[java] view plain copy
  1. public  void com.iflytek.Student3.fun()  

  2. public  void com.iflytek.Student3.talk()  


如果把上述代码中的getDeclaredMethods()换成getMethods(),打印出来的结果如下:

[java] view plain copy
  1. public  void com.iflytek.Student3.fun()  

  2. public  void com.iflytek.Student3.talk()  

  3. public  final  void java.lang.Object.wait( long, intthrows java.lang.InterruptedException  

  4. public  final  native  void java.lang.Object.wait( longthrows java.lang.InterruptedException  

  5. public  final  void java.lang.Object.wait()  throws java.lang.InterruptedException  

  6. public  boolean java.lang.Object.equals(java.lang.Object)  

  7. public java.lang.String java.lang.Object.toString()  

  8. public  native  int java.lang.Object.hashCode()  

  9. public  final  native java.lang.Class java.lang.Object.getClass()  

  10. public  final  native  void java.lang.Object.notify()  

  11. public  final  native  void java.lang.Object.notifyAll()  

使用getMethods()方法时,不仅获取到了Student3类中的方法,Object类中的所有方法也被获取到。


上面的程序是直接调用了Method类中的toString()方法输出的,输出格式并不是很理想,没有异常等相关信息。如果有需要,我们也可以自己整理拼接方法输出。

需要用到Method类中的如下几种方法:

getModifiers():以整数形式返回此 Method 对象所表示方法的 Java 语言修饰符。

getReturnType():返回一个Class对象,该对象描述了此Method对象所表示的方法的正式返回类型。

getName():以String形式返回此Method对象表示的方法名称。

getParameterTypes():按照声明顺序返回Class对象的数组,这些对象描述了此Method对象所表示的方法的形参类型。

getExceptionTypes():返回Class对象的数组,这些对象描述了声明将此Method对象表示的底层方法抛出的异常类型。

在这需要注意的是,利用getModifiers()获取修饰符并不是简单的输出public、static等,而是以整数形式返回所表示的方法的Java语言修饰符。可借助 Modifier类的toString()方法来完成。

自己手动拼接输出类中的方法信息的具体代码如下:

[java] view plain copy
  1. import java.lang.reflect.Method;  

  2. import java.lang.reflect.Modifier;  

  3.   

  4. interface Message {  

  5.      public  void get();  

  6. }  

  7.   

  8. class Student1  implements Message {  

  9.   

  10.      public  void fun() {  

  11.     }  

  12.   

  13.      public  void print() {  

  14.     }  

  15.   

  16.      public  void get() {  

  17.     }  

  18. }  

  19.   

  20. public  class ReflectMethodDemo {  

  21.   

  22.      public  static  void main(String[] args)  throws Exception {  

  23.         Class<?> cls = Class.forName( "com.iflytek.Student1");  

  24.         Method[] me = cls.getMethods();  

  25.          for ( int i =  0; i < me.length; i++) {  

  26.              // 此时用了method的toString方法输出,如果有需要,用户也可以自己拼凑输出  

  27.              // System.out.println(me[i]);  

  28.              // 取得修饰符  

  29.             System.out.print(Modifier.toString(me[i].getModifiers()) +  " ");  

  30.              // 取得返回值类型  

  31.             System.out.print(me[i].getReturnType().getSimpleName() +  " ");  

  32.              // 取得方法名称  

  33.             System.out.print(me[i].getName() +  "(");  

  34.              // 取得方法参数  

  35.             Class<?> params[] = me[i].getParameterTypes();  

  36.              if (params.length >  0) {  

  37.                  for ( int j =  0; j < params.length; j++) {  

  38.                     System.out.print(params[j].getSimpleName() +  " arg-" + j);  

  39.                      if (j < params.length -  1) {  

  40.                         System.out.print( ", ");  

  41.                     }  

  42.                 }  

  43.             }  

  44.             System.out.print( ") ");  

  45.              // 取得异常  

  46.             Class<?>[] exp = me[i].getExceptionTypes();  

  47.              if (exp.length >  0) {  

  48.                 System.out.print( "throws ");  

  49.                  for ( int j =  0; j < exp.length; j++) {  

  50.                     System.out.print(exp[j].getSimpleName());  

  51.                      if (j < exp.length -  1) {  

  52.                         System.out.println( ", ");  

  53.                     }  

  54.                 }  

  55.             }  

  56.             System.out.println( "{}");  

  57.             System.out.println();  

  58.         }  

  59.     }  

  60. }  

打印结果:

[java] view plain copy
  1. public  void get() {}  

  2.   

  3. public  void print() {}  

  4.   

  5. public  void fun() {}  

  6.   

  7. public  final  void wait( long arg- 0int arg- 1throws InterruptedException{}  

  8.   

  9. public  final  native  void wait( long arg- 0throws InterruptedException{}  

  10.   

  11. public  final  void wait()  throws InterruptedException{}  

  12.   

  13. public  boolean equals(Object arg- 0) {}  

  14.   

  15. public String toString() {}  

  16.   

  17. public  native  int hashCode() {}  

  18.   

  19. public  final  native Class getClass() {}  

  20.   

  21. public  final  native  void notify() {}  

  22.   

  23. public  final  native  void notifyAll() {}  


通过打印的结果来看,手动拼接的没有直接获取的简单粗暴,看起来比较舒服,该有的都有,自己想要什么结果就可以拼接什么结果。

上面的代码一般在编写开发工具中实现,随笔提示就是此类代码的。上面所说的内容在在我们开发过程中是很少用到,那么肯定有人不明白,既然开发过程中用不到,为什么要说这么多了?

前面的知识都是让大家对Java的反射机制有个更好的认识,以及反射的基本使用方法。前面的算是热身,接下来要给大家介绍一个反射中比较重要的方法。


5.反射中的invoke方法

调用(invoke):对带有指定参数的指定对象调用由此Method对象表示的底层方法。

[java] view plain copy
  1. public Object invoke(Object obj,  

  2.                      Object... args)  

  3.                throws IllegalAccessException,  

  4.                      IllegalArgumentException,  

  5.                      InvocationTargetException  

该方法的英文名字是invoke,中文名称就叫“调用”,该方法在Method类中,Method类本身就代表一个方法,当Method类中的对象调用invoke方法时,就相当于调用了Method对象所代表的方法,方法里面传入对应的参数,实现动态调用方法。这里可能比较难理解,看一下一个简单的实例:

[java] view plain copy
  1. import java.lang.reflect.Method;  

  2.   

  3. class Student4 {  

  4.      private String name;  

  5.   

  6.      public String getName() {  

  7.          return name;  

  8.     }  

  9.   

  10.      public  void setName(String name) {  

  11.          this.name = name;  

  12.     }  

  13. }  

  14.   

  15. public  class ReflectInvokeDemo {  

  16.      public  static  void main(String[] args)  throws Exception {  

  17.         Class<?> cls = Class.forName( "com.iflytek.Student4");  

  18.          // 实例化对象  

  19.         Object obj = cls.newInstance();  

  20.          //获取setName()方法  

  21.         Method setNameMethod = cls.getMethod( "setName", String. class);  

  22.          //获取getName()方法  

  23.         Method getNameMethod = cls.getMethod( "getName");  

  24.          //调用setName()方法,相当于 对象.setName("马小超");  

  25.         setNameMethod.invoke(obj,  "马小超");  

  26.          //调用getName()方法并输出  

  27.         System.out.println(getNameMethod.invoke(obj));  

  28.     }  

  29. }  


上面的例子简单的实现了invoke的用法,在动态代理中和Spring框架中都用到了invoke方法,可以实现方法的动态调用,所以在程序设计时使用很广泛。


6.利用反射调用类中的属性

调用类中的属性和调用类中的方法差不多,也是分为两大类,每个大类里面分为两小类,如下:

获取包括继承而来的属性:

         获取全部属性:

[java] view plain copy
  1. public Field[] getFields()  

  2.                    throws SecurityException  

        获取指定属性:

[java] view plain copy
  1. public Field getField(String name)  

  2.                 throws NoSuchFieldException,  

  3.                       SecurityException  


获取本类定义的属性:

        获取全部属性:

[java] view plain copy
  1. public Field[] getDeclaredFields()  

  2.                            throws SecurityException  

        获取指定的属性:

[java] view plain copy
  1. public Field getDeclaredField(String name)  

  2.                         throws NoSuchFieldException,  

  3.                               SecurityException  


利用反射获取类中的属性,是不提倡使用的,因为违背了面向对象的封装特性。
在Field类中定义了进行属性调