• 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

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

  • 相关阅读:
    新数学丛书《连分数》 习题 1.1
    连分数中一个有意思的小玩意儿
    无聊博文之:用同余的语言阐述欧几里德算法
    有向无环图
    Codeforces Round #658 (Div. 2)
    常用代码模板3——搜索与图论
    什么是动态规划?动态规划的意义是什么?(转自知乎)
    Codeforce:4C. Registration system (映射)
    C++与正则表达式入门
    常用代码模板4——数学知识
  • 原文地址:https://www.cnblogs.com/jiangzhaowei/p/7391255.html
Copyright © 2020-2023  润新知