JAVA 反射

概述

我们知道,Java不是一种动态语言,它在运行中产生的一些新的东西是没办法控制的,如果某些类型或者接口是在我们编写程序时不存在的,我们对这种类型的内容一无所知,甚至是名字,所以我们并没有办法通过new一个对象来编写程序,那么我们怎么利用它里面的属性或者方法呢,这时候,就产生了反射机制。

反射机制是针对内存中运行的字节码文件进行操作的机制,当内存中产生了字节码,我们就可以根据这份字节码获取其对应的类、属性以及方法,了解了这些之后,我们就可以根据具体的内容编写程序了。

但反射机制只是对已有的字节码进行操作,而不能自己创造一份字节码出来,也就是说Class并没有支持的构造函数来干这个事,这让我疑惑不已。

Class类

Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(booleanbytecharshortintlongfloat 和 double)和关键字 void 也表示为 Class 对象。

简单的说,Class类代表的就是运行中的一份类字节码

那么如何获取一个运行中的Class字节码对象呢?

  1. 通过类名获得:类名.class
  2. 通过对象获得:对象.getClass()
  3. 通过字符串代表的类名获得:Class.forName(“完整的类名“)
   1: //通过类名直接获得
   2: System.out.println(Date.class);
   3: //通过对象获得
   4: System.out.println(new Date().getClass());
   5: //通过一个字符串类名获得
   6: //此处存在ClassNotFoundException异常
   7: try {
   8:     //这里注意:需要完整的类名
   9:     System.out.println(Class.forName("java.util.Date"));
  10: } catch (ClassNotFoundException e) {
  11:     // TODO Auto-generated catch block
  12:     e.printStackTrace();
  13: }

打印结果:

class java.util.Date
class java.util.Date
class java.util.Date

由于是对未知的字节码文件进行操作,所以我们一般只能获得代表类名的字符串,所以一般采用第三种方式获取类的字节码对象

那么既然我们得到了类的字节码文件,类中的各个成员属性我们也就可以得到了

Method类代表类中的方法属性

Field类代表类中的各个字段

Constructor类代表类中的构造函数

下面我们分别介绍

这里提供一个ReflectPoint类用作测试

   1: public class ReflectPoint {
   2:     public int x = 1;
   3:     private int y = 2;
   4:     
   5:     public ReflectPoint(){
   6:         
   7:     }
   8:     
   9:     public ReflectPoint(int x, int y) {
  10:         super();
  11:         this.x = x;
  12:         this.y = y;
  13:     }
  14:     
  15:     public void method(){
  16:         System.out.println(x+":"+y);
  17:     }
  18:  
  19:     @Override
  20:     public String toString() {
  21:         return "ReflectPoint ["+x+","+y+"]";
  22:     }
  23:     
  24: }

Constructor类

public final class Constructor<T>
extends AccessibleObject
implements GenericDeclarationMember

Constructor 提供关于类的单个构造方法的信息以及对它的访问权限。

通过反射实例化对象

   1: public class ConstructorTest {
   2:  
   3:     /**
   4:      * @param args
   5:      * @throws NoSuchMethodException 
   6:      * @throws InvocationTargetException 
   7:      * @throws IllegalAccessException 
   8:      * @throws InstantiationException 
   9:      * @throws SecurityException 
  10:      * @throws IllegalArgumentException 
  11:      */
  12:     public static void main(String[] args) throws ClassNotFoundException, IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  13:         
  14:         //获取类的字节码对象
  15:         String className = "reflect.ReflectPoint";
  16:         Class cls = Class.forName("reflect.ReflectPoint");
  17:         
  18:         //获取类中的所有的构造函数对象
  19:         Constructor[] cons = cls.getConstructors();    
  20:         System.out.println("---------构造函数列表----------");
  21:         for(Constructor con : cons){
  22:             Class[] paramTypes = con.getParameterTypes();
  23:             String consName = con.getName();
  24:             System.out.println(consName + Arrays.toString(paramTypes));
  25:         }
  26:         System.out.println("-------------------------------");
  27:         //获取无参的构造函数,并实例化对象
  28:         Constructor conNoParam = cls.getConstructor();
  29:         Object objNoParam = conNoParam.newInstance();
  30:         System.out.println("无参实例对象:"+objNoParam);
  31:         
  32:         //获取有参的构造函数,并实例化对象
  33:         Constructor conParam = cls.getConstructor(int.class,int.class);
  34:         Object objParam = conParam.newInstance(2,2);
  35:         System.out.println("有参实例对象:"+objParam);
  36:         System.out.println("-------------------------------");
  37:         
  38:         //通过Class类直接实例对象,此方法只针对无参构造函数
  39:         System.out.println("Class获取无参实例对象:"+cls.newInstance());    
  40:     }
  41: }

打印结果:

---------构造函数列表----------
reflect.ReflectPoint[]
reflect.ReflectPoint[int, int]
-------------------------------
无参实例对象:ReflectPoint [1,2]
有参实例对象:ReflectPoint [2,2]
-------------------------------
Class获取无参实例对象:ReflectPoint [1,2]

同样,我们也可以将获取对象的操作抽取成方法,以便以后使用

   1: public class ConstructorTest2 {
   2:  
   3:     /**
   4:      * @param args
   5:      * @throws ClassNotFoundException 
   6:      */
   7:     public static void main(String[] args) throws ClassNotFoundException {
   8:         //获取类的字节码对象
   9:         String className = "reflect.ReflectPoint";
  10:         Class cls = Class.forName(className);
  11:         
  12:         //获取无参对象
  13:         Object obj1 = getInstance(cls, null, null);
  14:         System.out.println(obj1);
  15:         
  16:         //获取有参对象
  17:         Class[] parameterTypes = new Class[]{int.class,int.class};
  18:         Object[] initargs = new Object[]{2,2};
  19:         Object obj2 = getInstance(cls, parameterTypes, initargs);
  20:         System.out.println(obj2);
  21:  
  22:     }
  23:     
  24:     /**
  25:      * 获取指定类指定构造函数的实例化对象
  26:      * 
  27:      * @param target 目标类的字节码对象
  28:      * @param parameterTypes 需要的构造函数参数类型
  29:      * @param initargs 要传入的参数列表
  30:      * @return 目标类的对象
  31:      */
  32:     private static Object getInstance(Class target,Class[] parameterTypes,Object[] initargs) {
  33:         Object obj = null;
  34:         try {
  35:             Constructor con = target.getConstructor(parameterTypes);
  36:             obj = con.newInstance(initargs);
  37:         } catch (SecurityException e) {
  38:             // TODO Auto-generated catch block
  39:             e.printStackTrace();
  40:         } catch (IllegalArgumentException e) {
  41:             // TODO Auto-generated catch block
  42:             e.printStackTrace();
  43:         } catch (NoSuchMethodException e) {
  44:             // TODO Auto-generated catch block
  45:             e.printStackTrace();
  46:         } catch (InstantiationException e) {
  47:             // TODO Auto-generated catch block
  48:             e.printStackTrace();
  49:         } catch (IllegalAccessException e) {
  50:             // TODO Auto-generated catch block
  51:             e.printStackTrace();
  52:         } catch (InvocationTargetException e) {
  53:             // TODO Auto-generated catch block
  54:             e.printStackTrace();
  55:         }
  56:         return obj;
  57:     }
  58: }

Field类

public final class Field
extends AccessibleObject
implements Member

Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。

以下代码通过反射实现获取和设置对象的属性值

   1: public class FieldTest {
   2:  
   3:     /**
   4:      * @param args
   5:      * @throws ClassNotFoundException 
   6:      * @throws IllegalAccessException 
   7:      * @throws InstantiationException 
   8:      */
   9:     public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
  10:         
  11:         //获取类的字节码对象并创建对象
  12:         String className = "reflect.ReflectPoint";
  13:         Class cls = Class.forName(className);
  14:         Object obj = cls.newInstance();
  15:         
  16:         //获取x属性并修改x属性
  17:         String fieldName = "x";
  18:         System.out.println(getFieldValue(obj, fieldName));
  19:         setFiledValue(obj, fieldName, 2);
  20:         System.out.println(getFieldValue(obj, fieldName));
  21:         
  22:         //获取y属性并修改y属性,由于y是私有的,所以外部不可以设置
  23:         /*fieldName = "y";
  24:         System.out.println(getFieldValue(obj, fieldName));
  25:         setFiledValue(obj, fieldName, 3);
  26:         System.out.println(getFieldValue(obj, fieldName));*/
  27:          
  28:         
  29:         
  30:     }
  31:     /**
  32:      * 设置对象字段值
  33:      * @param obj 需要设置的对象
  34:      * @param fieldName 需要设置的字段名
  35:      * @param value 需要设置的属性值
  36:      */
  37:     private static void setFiledValue(Object obj,String fieldName,Object value){
  38:         Class cls = obj.getClass();
  39:         try {
  40:             //根据字段名获取字段
  41:             Field field = cls.getField(fieldName);
  42:             //设置字段属性
  43:             field.set(obj, value);
  44:         } catch (SecurityException e) {
  45:             // TODO Auto-generated catch block
  46:             e.printStackTrace();
  47:         } catch (IllegalArgumentException e) {
  48:             // TODO Auto-generated catch block
  49:             e.printStackTrace();
  50:         } catch (NoSuchFieldException e) {
  51:             // TODO Auto-generated catch block
  52:             e.printStackTrace();
  53:         } catch (IllegalAccessException e) {
  54:             // TODO Auto-generated catch block
  55:             e.printStackTrace();
  56:         }
  57:     }
  58:     
  59:     /**
  60:      * 获取对象字段值
  61:      * @param obj 需要获取值的对象
  62:      * @param fieldName 需要获取的属性名
  63:      * @return
  64:      */
  65:     private static Object getFieldValue(Object obj,String fieldName){
  66:         Class cls = obj.getClass();
  67:         Object retVal = null;
  68:         try {
  69:             //根据字段名获取字段
  70:             Field field = cls.getField(fieldName);
  71:             //获取字段属性
  72:             retVal = field.get(obj);
  73:         } catch (SecurityException e) {
  74:             // TODO Auto-generated catch block
  75:             e.printStackTrace();
  76:         } catch (IllegalArgumentException e) {
  77:             // TODO Auto-generated catch block
  78:             e.printStackTrace();
  79:         } catch (NoSuchFieldException e) {
  80:             // TODO Auto-generated catch block
  81:             e.printStackTrace();
  82:         } catch (IllegalAccessException e) {
  83:             // TODO Auto-generated catch block
  84:             e.printStackTrace();
  85:         }
  86:         return retVal;
  87:     }
  88: }

一个小例题,通过反射将任意对象中所有可读String字段中的”a”转成”b”

   1: public class FieldTest2 {
   2:  
   3:     /**
   4:      * @param args
   5:      * @throws ClassNotFoundException 
   6:      * @throws IllegalAccessException 
   7:      * @throws InstantiationException 
   8:      */
   9:     public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
  10:         
  11:         //获取类的字节码对象并创建对象
  12:         String className = "reflect.Code";
  13:         Class cls = Class.forName(className);
  14:         Object obj = cls.newInstance();
  15:         
  16:         System.out.println(obj);
  17:         changeStringValue(obj);
  18:         System.out.println(obj);
  19:         
  20:     }
  21:     
  22:     /**
  23:      * 通过反射方法将类中可读String字段中的”a“转为”b“
  24:      * @param obj
  25:      */
  26:     private static void changeStringValue(Object obj){
  27:         //获取类字节码对象
  28:         Class cls = obj.getClass();
  29:         //获取类中所有字段
  30:         Field[] fields = cls.getFields();
  31:         //遍历字段,找到String类型字段,并修改
  32:         for(Field field : fields){
  33:             if(field.getType() == String.class){
  34:                 try {
  35:                     String s = (String)field.get(obj);
  36:                     s = s.replaceAll("a", "b");
  37:                     field.set(obj, s);
  38:                 } catch (IllegalArgumentException e) {
  39:                     // TODO Auto-generated catch block
  40:                     e.printStackTrace();
  41:                 } catch (IllegalAccessException e) {
  42:                     // TODO Auto-generated catch block
  43:                     e.printStackTrace();
  44:                 }
  45:             }
  46:         }
  47:     }
  48: }

Method类

public final class Method
extends AccessibleObject
implements GenericDeclarationMember

Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。

下面我们来看看如何使用方法反射

   1: public class MethodTest {
   2:  
   3:     /**
   4:      * @param args
   5:      */
   6:     public static void main(String[] args) {
   7:         
   8:         //通过反射执行String对象的charAt方法
   9:         String str = "abcde";
  10:         Object obj = runMethod(String.class,str,"charAt",new Class[]{int.class},new Object[]{1});
  11:         System.out.println(obj);
  12:         
  13:         //通过反射执行Math的max方法
  14:         obj = runMethod(Math.class,null,"max",new Class[]{double.class,double.class},new Object[]{1.0,2.0});
  15:         System.out.println(obj);
  16:         
  17:     }
  18:     /**
  19:      * 执行任意对象的任意方法
  20:      * @param cls 类字节码对象
  21:      * @param obj 需要操作的对象
  22:      * @param methodName 方法名
  23:      * @param parameterTypes 方法参数列表类型
  24:      * @param args 需要传入的自定义参数列表
  25:      * @return
  26:      */
  27:     private static Object runMethod(Class cls,Object obj,String methodName,Class[] parameterTypes,Object[] args){
  28:         Object retVal = null;
  29:         try {
  30:             //通过方法名和参数列表获取方法对象
  31:             Method method = cls.getMethod(methodName, parameterTypes);
  32:             //通过方法的invoke方法执行对应对象的方法,同时传入参数列表
  33:             retVal = method.invoke(obj, args);
  34:         } catch (SecurityException e) {
  35:             // TODO Auto-generated catch block
  36:             e.printStackTrace();
  37:         } catch (IllegalArgumentException e) {
  38:             // TODO Auto-generated catch block
  39:             e.printStackTrace();
  40:         } catch (NoSuchMethodException e) {
  41:             // TODO Auto-generated catch block
  42:             e.printStackTrace();
  43:         } catch (IllegalAccessException e) {
  44:             // TODO Auto-generated catch block
  45:             e.printStackTrace();
  46:         } catch (InvocationTargetException e) {
  47:             // TODO Auto-generated catch block
  48:             e.printStackTrace();
  49:         }
  50:         return retVal;
  51:     }
  52: }

我们注意到,当Method的invoke方法传入的对象引用为空时,其实代表的是类中的静态方法,因为静态方法可以直接由类名调用

Array类

数组的反射

在Class类的API文档里我们发现,只要是数据类型和维度都相同的数组,是共用同一份字节码的,下面的例子说明了这一问题

   1: public static void main(String[] args) {
   2:     // TODO Auto-generated method stub
   3:     int[] a = new int[2];
   4:     int[] b = new int[3];
   5:     double[][] c = new double[3][];
   6:     double[][] d = new double[4][];
   7:     
   8:     //比较两份字节码是否相同
   9:     System.out.println(a.getClass() == b.getClass());
  10:     System.out.println(c.getClass() == d.getClass());
  11:     
  12:     //这句eclipse会自动判定编译错误
  13:     //Incompatible operand types Class<capture#5-of ? extends int[]> 
  14:     //and Class<capture#6-of ? extends double[][]>
  15:     System.out.println(a.getClass() == c.getClass());
  16: }

打印结果:

true

true

Array类应用

   1: public class ArrayReflectTest {
   2:  
   3:     /**
   4:      * @param args
   5:      */
   6:     public static void main(String[] args) {
   7:         
   8:         print(new String[]{"1","2"});
   9:         System.out.println();
  10:         print(new int[][]{{1,2,3},{4,5,6}});
  11:     }
  12:     
  13:     //打印对象,若为数组,则打印数组中元素
  14:     private static void print(Object obj){
  15:         Class cls = obj.getClass();
  16:         //判断所属类是否为数组类型
  17:         if(cls.isArray()){
  18:             //Array获取数组长度
  19:             int length = Array.getLength(obj);
  20:             for(int i = 0 ; i < length ; i ++){
  21:                 //若元素仍为数组,即多维数组,则递归打印
  22:                 Object objTemp = Array.get(obj, i);
  23:                 if(objTemp.getClass().isArray())
  24:                     print(objTemp);
  25:                 else
  26:                     System.out.print(objTemp+" ");
  27:             }
  28:         }
  29:         else{
  30:             System.out.println(obj);
  31:         }
  32:     }
  33: }

好了,既然方法类和数组类都知道了,来处理一个小问题:参数为数组的方法的反射

请看下面例子,调用其他类的main函数引出该问

   1: class MainMethod{
   2:     public static void main(String[] args){
   3:         for(int i = 0 ; i < args.length ; i ++){
   4:             System.out.println(args[i]);
   5:         }
   6:     }
   7: }
   8:  
   9: public class MainTest {
  10:  
  11:     /**
  12:      * @param args
  13:      * @throws ClassNotFoundException 
  14:      * @throws NoSuchMethodException 
  15:      * @throws SecurityException 
  16:      * @throws InvocationTargetException 
  17:      * @throws IllegalAccessException 
  18:      * @throws IllegalArgumentException 
  19:      */
  20:     public static void main(String[] args) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
  21:         // TODO Auto-generated method stub
  22:         Class cls = Class.forName("reflect.MainMethod");
  23:         Method mainMethod = cls.getMethod("main", String[].class);
  24:         mainMethod.invoke(null, new String[]{"1","2"});
  25:     }
  26: }

上面的例子,会报错

Exception in thread "main" java.lang.IllegalArgumentException: wrong number of arguments

之所以报错,是因为JDK1.5中Method的invoke方法接收的是可变参数Object… params,而JDK1.4中Method的invoke方法接收的是数组Object[] params,我们知道JDK1.5要向下兼容1.4的方法,所以当传入String类型数组时,JVM并不会将这个数组作为可变参数的第一个参数,而是按照JDK1.4的方法,将String数组转换成Object数组拆包,这样就相当于给main方法传递了两个参数”1”和”2”,而main方法只有一个String[]参数,所以出现了不合法的参数异常,那么既然知道了原因,修改方法也自然出来了

根据JDK1.4的修改方法:mainMethod.invoke(null, new Object[]{new String[]{"1","2"}});即为将参数列表封装成Object数组传递给invoke,令其拆包

根据JDK1.5的修改方法:mainMethod.invoke(null, (Object)new String[]{"1","2"});即为将参数列表的元素封装成Object对象,使可变参数接收

反射的意义

说了这么多反射的应用,但是究竟反射有何意义呢?

其实意义就在于它产生的原因,有了反射,就可以控制一些运行中不可见的因素,大大的提高了程序的扩展性,

今后的框架也是基于反射做的,开发框架的人在开发时并不知道需要调用哪些类,因为这些类都是使用者自己写的,他们根据反射将使用者将来写出的类做了分析,写出框架,而我们在使用框架的过程中只需要写自己的类,并将类的相关属性配置到框架的配置文件中即可

下面是一段模拟框架的小代码,包括框架代码和一个property配置文件

   1: public class FrameTest {
   2:  
   3:     /**
   4:      * @param args
   5:      * @throws IOException 
   6:      * @throws ClassNotFoundException 
   7:      * @throws IllegalAccessException 
   8:      * @throws InstantiationException 
   9:      */
  10:     public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
  11:  
  12:         //通过Properties与字节流配合获取配置文件信息
  13:         InputStream ips = new FileInputStream("config.properties");
  14:         Properties prop = new Properties();
  15:         prop.load(ips);
  16:         //得到className
  17:         String className = prop.getProperty("className");
  18:         
  19:         //通过className创建对象,此处利用接口编程
  20:         Collection col = (Collection) Class.forName("java.util.ArrayList").newInstance();
  21:         col.add(1);
  22:         col.add(2);
  23:         col.add(3);
  24:         
  25:         System.out.println(col);
  26:     }
  27: }
配置文件为config.properties
里面内容为

className = java.util.ArrayList

这就是一个简单的框架,利用接口编程,当我们需要更改使用的集合类时,只需要在配置文件中更改即可,非常方便

原文地址:https://www.cnblogs.com/jiangzhaowei/p/7391255.html