• Java基础(九)反射(reflection)


      1.反射库(reflection library)提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵Java代码的程序。

      能够分析类能力的程序称为反射(reflection)。反射机制的功能极其强大,例如:

    • 在运行时分析类的能力
    • 在运行时查看对象
    • 实现通用的数组操作代码
    • 利用Method对象

      

      2.Class类

      在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。然而,可以通过Java类访问这些信息,保存这些信息的类被称为Class。

      获得Class类的实例一共有三种方法:

      (1)Object类中的getClass方法将会返回一个Class类型的实例,一个Class类型的实例表示一个特定类的属性,最常用的Class方法时getName方法,这个方法将返回类的名字。

          Class class1 = alice1.getClass();
          System.out.println(class1.getName());  // 打印:包名.Emplayee
          Class class2 = carl.getClass();
          System.out.println(class2.getName());  // 打印:包名.Manager

      (2)可以通过静态方法forName获得类名对应的Class对象:如果类名保存在字符串中,并想要在运行中改变,就可以使用这个方法。这个方法只能在className是类名或接口名时才能够执行。否则,将抛出一个checked exception异常。因此,使用这个方法的时候需要提供一个异常处理器。

          String className = "java.util.Random";
          Class class3 = Class.forName(className);
          System.out.println(class3.getName());  // 打印:java.util.Random

      实际应用时应该提供一个异常处理器:

          try
          {
             // print class name and superclass name (if != Object)
             Class cl = Class.forName(name);
          do something with cl;
          }
          catch (Exception e)
          {
             e.printStackTrace();
          }

      (3)如果T是任意的Java类型,则T.class将代表匹配的类对象。一个Class类对象实际上表示的是一个类型,而这个类型未必一定是一种类。int不是类,但int.class是一个Class类型的对象。

          Class cl1 = int.class;
          System.out.println(cl1.getName());  // 打印:int
          Class cl2 = Double.class;
          System.out.println(cl2.getName());  // 打印:java.lang.Double

      其他Class方法:

      (1)比较两个类对象:

    Employee e;
    if(e.getClass() == Employee.class) ...;

      (2)动态地创建一个类的实例:newInstance方法调用类的默认的构造器,即没有参数的构造器初始化新创建的类对象。如果没有默认构造器,就抛出一个异常。

    e.getClass().newInstance();

      (3)将forName与newInstance配合使用,就可以创建执行类名的类的一个实例

    String className = "java.util.Random";
    Object m = Class.forName(s).newInstance();

      3.利用反射分析类的能力

      反射机制最重要的就是检查类的结构。

      (1)打印含修饰符的类名和父类类名

             // print class name and superclass name (if != Object)
             Class cl = Class.forName(name);      
             System.out.println(cl.getName());                // 打印:java.lang.Double
             Class supercl = cl.getSuperclass();
             System.out.println(supercl.getName());             // 打印:java.lang.Number
    // getModifiers()方法将返回一个整型数值,用不同的位开关描述public和static这样的修饰符使用情况。
    // Modifier类还有isPublic、isPrivate等等用来判断方法或构造器是否是Public、Private等等
             System.out.println(cl.getModifiers());             // 打印:17
             String modifiers = Modifier.toString(cl.getModifiers());  // 打印:public final
             System.out.println(modifiers);
             if (modifiers.length() > 0) System.out.print(modifiers + " ");
             System.out.print("class " + name);
             if (supercl != null && supercl != Object.class) System.out.print(" extends "
                   + supercl.getName());

    输入:String name = “java.lang.Double”
    输出:public final class java.lang.Double extends java.lang.Number

      (2)打印类中所有的构造器名

       /**
        * Prints all constructors of a class
        * @param cl a class
        */
       public static void printConstructors(Class cl)
       {
          Constructor[] constructors = cl.getDeclaredConstructors(); // 返回全部构造器,不包括由超类继承的 
                                           // getConstructors()将返回类提供的构造器数组,包括超类的构造器
          for (Constructor c : constructors)
          {
             String name = c.getName();
             System.out.print("   ");
             String modifiers = Modifier.toString(c.getModifiers());
             if (modifiers.length() > 0) System.out.print(modifiers + " ");         
             System.out.print(name + "(");
    
             // print parameter types
             Class[] paramTypes = c.getParameterTypes();
             for (int j = 0; j < paramTypes.length; j++)
             {
                if (j > 0) System.out.print(", ");
                System.out.print(paramTypes[j].getName());
             }
             System.out.println(");");
          }
       }

    输出:
       public java.lang.Double(double);
       public java.lang.Double(java.lang.String)

      (3)打印类中所有的方法名

       /**
        * Prints all methods of a class
        * @param cl a class
        */
       public static void printMethods(Class cl)
       {
          Method[] methods = cl.getDeclaredMethods();     // 返回全部方法,不包括由超类继承的 
    
          for (Method m : methods)
          {
             Class retType = m.getReturnType();
             String name = m.getName();
    
             System.out.print("   ");
             // print modifiers, return type and method name
             String modifiers = Modifier.toString(m.getModifiers());
             if (modifiers.length() > 0) System.out.print(modifiers + " ");         
             System.out.print(retType.getName() + " " + name + "(");
    
             // print parameter types
             Class[] paramTypes = m.getParameterTypes();
             for (int j = 0; j < paramTypes.length; j++)
             {
                if (j > 0) System.out.print(", ");
                System.out.print(paramTypes[j].getName());
             }
             System.out.println(");");
          }
       }

    输出:
       public boolean equals(java.lang.Object);
       public static java.lang.String toString(double);
       public java.lang.String toString();
      ...

      (3)打印类中所有的域名

       /**
        * Prints all fields of a class
        * @param cl a class
        */
       public static void printFields(Class cl)
       {  
          Field[] fields = cl.getDeclaredFields();    // 返回全部域,不包括由超类继承的
    
          for (Field f : fields)
          {
             Class type = f.getType();                        // 域类型
             String name = f.getName();                       // 域名
             System.out.print("   ");
             String modifiers = Modifier.toString(f.getModifiers());      // 修饰符
             if (modifiers.length() > 0) System.out.print(modifiers + " ");         
             System.out.println(type.getName() + " " + name + ";");
          }
       }

    输出:
       public static final double POSITIVE_INFINITY;
       public static final double NEGATIVE_INFINITY;
       public static final double NaN;
      ...
      private final double value;
       private static final long serialVersionUID

      4.在运行时使用反射分析对象

      在前面的代码中,已经得到了全部域的域名和类型,如何查看数据域的实际内容是分析对象的重要内容。

      一个例子如下,还是以Employee类为例,Employee类有三个域,它们都是私有域

    public class Employee
    {
       private String name;
       private double salary;
       private LocalDate hireDay;
       ...
    }

      使用getDeclaredField("name");带参数的方法可以得到Field类型的域name域对象f,然后通过f.get(obj)方法,其中obj是某个包含f域的类的对象,就可以返回一个对象,这个对象就是obj的f域。但是,这个时候会抛出一个IllegalAccessException异常,是因为name域是私有域。这时,除非有访问权限,否则Java安全机制不允许读取私有域的值。

          Employee alice1 = new Employee("Alice Adams", 75000, 1987, 12, 15);
          Class class1 = alice1.getClass();
          Field f = class1.getDeclaredField("name");
          Object vObject = f.get(alice1);
    输出:Exception in thread "main" java.lang.IllegalAccessException: Class bbbTest.EqualsTest can not access a member of class bbbTest.Employee with modifiers "private"...

      反射机制的默认行为受限于Java的访问控制。然而,如果一个Java程序没有收到安全管理器的控制,就可以覆盖访问控制。这是,可以调用Field、Method或Constructor对象的setAccessible方法可设置为可以访问。

          Employee alice1 = new Employee("Alice Adams", 75000, 1987, 12, 15);
          Class class1 = alice1.getClass();
          Field f = class1.getDeclaredField("name");
          f.setAccessible(true);
          Object vObject = f.get(alice1);
          System.out.println(vObject);  // 打印:Alice Adams

      加入要查看double类型的salary域,这时因为double是数值类型,而不是对象类型,所以要想解决这个问题,可以使用Field类中的getDouble方法,也可以调用get方法,这时,反射机制将会自动地将这个域值打包到相应的对象包装器找那个,这里将打包成Double。

      在get到域值后,可以使用set将obj对象的f域设置成新值。

          Employee alice1 = new Employee("Alice Adams", 75000, 1987, 12, 15);
          Class class1 = alice1.getClass();
          Field f = class1.getDeclaredField("name");
          f.setAccessible(true);
          Object vObject = f.get(alice1);
          System.out.println(vObject);
          f.set(alice1, "Lian Jiang");
          Object vObject1 = f.get(alice1);
          System.out.println(vObject1);       // 打印:Lian Jiang

      5.使用反射编写泛型数组

      java.lang.reflect包中的Array类允许动态地创建数组。其中,可以使用Array类中的newInstance方法,这个方法可以构造新数组,在调用它时必须提供两个参数,一个是数组的元素类型,另一个是数组的长度。

    Object newArray = Array.newInstance(componentType, newLength);

      因此,现在有两步要做:1.获得数组的长度。2.获得数组的元素类型

      (1)获得数组的长度可以通过Array.getLength(a)或者Array类的静态getLength方法返回任意数组a的长度。

      (2)在前面已经知道,可以通过Class类的getComponentType方法确定数组对应的类型。

       public static Object goodCopyOf(Object a, int newLength) 
       {
          Class cl = a.getClass();          // 首先取得数组a的类对象
          if (!cl.isArray()) return null;        // 然后确认数组a是一个数组
          Class componentType = cl.getComponentType();     // 接着确定数组对应的类型
          int length = Array.getLength(a);
          Object newArray = Array.newInstance(componentType, newLength);  // 创建数组
          System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));  // 将原数组中的元素拷贝到新数组中
          return newArray;
       }

      例如:

          int[] a = { 1, 2, 3 };
          a = (int[]) goodCopyOf(a, 10);
          System.out.println(Arrays.toString(a));  // 打印:[1, 2, 3, 0, 0, 0, 0, 0, 0, 0]
    
          String[] b = { "Tom", "Dick", "Harry" };
          b = (String[]) goodCopyOf(b, 10);      // 打印:[Tom, Dick, Harry, null, null, null, null, null, null, null]
          System.out.println(Arrays.toString(b));

      6.使用反射调用任意方法

      (1)首先,使用Method类getMethod获得Method对象

          Method square = MethodTableTest.class.getMethod("square", double.class);
          System.out.println(square);  // 打印:public static double aaaaTest.MethodTableTest.square(double)
          Method sqrt = Math.class.getMethod("sqrt", double.class);
          System.out.println(sqrt);    // 打印:public static double java.lang.Math.sqrt(double)

      (2)然后通过invoke方法调用:f是square或sqrt对象,第一个参数null是因为是静态方法,第二个参数是调用的方法的参数。另外,invoke方法的参数和返回值必须是Object类型的,因此必须进行多次的类型转换。

    double x = 10.000;
    double
    y = (Double) sqrt.invoke(null, x);
    System.out.printf("%10.4f%n", y);  // 打印:3.1623
  • 相关阅读:
    如何关闭内存自动释放池ARC
    你怀疑过“温水煮青蛙”的故事吗
    程序员应该加入的3个QQ群
    简述Oracle 11g 新特性
    ViewState、UpdatePanel及控件OnPre之间的纠葛
    今天,我看到一组图解释“ 什么是博士?”
    Java将何去何从
    给新手朋友 推荐几本书(从C#入门到SQL及设计模式)
    最新版 智能电脑键盘屏幕全记录 免费下载
    C#中两个问号和一个问号
  • 原文地址:https://www.cnblogs.com/BigJunOba/p/9324798.html
Copyright © 2020-2023  润新知