• Java反射


    反射

    反射库( reflection library ) 提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵 Java 代码的程序。这项功能被大量地应用于 JavaBeans中,它是 Java组件的体系结构。

    能够分析类能力的程序称为反射(reflective )。反射机制的功能极其强大,在下面可以看到,反射机制可以用来:

    • 在运行时分析类的能力。
    • 在运行时查看对象,例如,编写一个 toString方法供所有类使用。
    • 实现通用的数组操作代码。
    • 利用 Method对象,这个对象很像中的函数指针。

    class类

    最常用的 Class方法是 getName。这个方法将返回类的名字。如果类在一个包里,包的名字也作为类名的一部分:包名.类名。

    还可以调用静态方法 forName 获得类名对应的 Class 对象。

    String dassName = "java.util .Random"; 
    Class cl = Class.forName(className) ;
    

    这个方法只有在 className是类名或接口名时才能够执行。否则,forName方法将抛出一个 checked exception (已检查异常)。无论何时使用这个方法,都应该提供一个异常处理器(exception handler ) o。

    请注意,一个 Class对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如, int 不是类,但 int.class 是一个 Class类型的对象。

    Class 类实际上是一个泛型类。例如, Employee.class 的类型是 Class

    虚拟机为每个类型管理一个 Class 对象。因此,可以利用=运算符实现两个类对象比较的操作。例如,if (e.getClass() == Employee,class) . . .

    还有一个很有用的方法 newlnstance( ),可以用来动态地创建一个类的实例例如:e.getClass0.newlnstance();创建了一个与 e具有相同类类型的实例。

    newlnstance方法调用默认的构造器(没有参数的构造器)初始化新创建的对象。如果这个类没有默认的构造器,就会抛出一个异常。

    捕获异常

    当程序运行过程中发生错误时,就会“抛出异常“抛出异常比终止程序要灵活得多,这是因为可以提供一个“ 捕获” 异常的处理器(handler ) 对异常情况进行处理。如果没有提供处理器,程序就会终止,并在控制台上打印出一条信息,其中给出了异常的类型。

    异常有两种类型:未检查异常和已检查异常。

    如果try块中抛出异常,则将跳过 try块中的剩余代码,程序直接进人catch 子句(这里,利用Throwable类的 printStackTrace 方法打印出栈的轨迹。Throwable 是 Exception 类的超类)。如果try块中没有抛出任何异常,那么会跳过 catch 子句的处理器代码。

    利用反射分析类的能力

    反射机制最重要的内容—检查类的结构。

    • 在java.lang.reflect 包中有三个类 Field、Method 和 Constructor分别用于描述类的域、方法和构造器。
      • Field类有一 个getType方法,用来返回描述域所属类型的 Class 对象。
      • Method类还有一个可以报告返回类型的方法。
      • Method 和 Constructor类有能够 报告参数类型的方法
      • 这三个类都有一个叫做 getName 的方法,用来返回项目的名称。
      • 这三个类还有一个叫做 getModifiers 的方法,它将返回一个整型数值,用不同的位开关描述 public 和 static 这样的修饰符使用状况。
        • 另外,还可以利用java.lang.reflect 包中的 Modifier类的静态方法分析 getModifiers 返回的整型数值。
        • 例如,可以使用 Modifier类中的 isPublic、 isPrivate 或 isFinal 判断方法或构造器是否是 public、 private 或 final。
        • 还可以利用 Modifier.toString方法将 修饰符打印出来。
    • Class类(java.lang.Object下java.lang.Class
      • Class类中的 getFields、 getMethods 和 getConstructors方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的公有成员
      • Class 类的 getDeclareFields、 getDeclareMethods 和 getDeclaredConstructors方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员

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

    查看任意对象的数据域名称和类型:

    • 获得对应的 Class 对象。
    • 通过 Class 对象调用 getDeclaredFields。

    利用反射机制可以查看在编译时还不清楚的对象域。

    查看对象域的关键方法是 Field类中的 get 方法:如果 f 是一个 Field类型的对象(例如,通过 getDeclaredFields 得到的对象),obj 是某个包含 f域的类对象,f.get(obj)将返回一个对象,其值为 obj 域的当前值。

    Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989) ; 
    Class cl = harry.getClass0; 
        // the class object representing Employee
    Field f = cl .getDeclaredFieldC'name") : 
        // the name field of the Employee class
    Object v = f.get (harry); 
    // the value of the name field of the harry object,i .e., the String object "Harry Hacker"
    

    实际上,这段代码存在一个问题:如果name是一个私有域,那么get 方法将会抛出一个 IllegalAccessException。

    反射机制的默认行为受限于 Java 的访问控制。除非拥有访问权限,否则 Java 安全机制只允许査看任意对象有哪些域,而不允许读取它们的值.

    然而,如果一个 Java 程序没有受到安全管理器的控制,就可以覆盖访问控制。为了达到这个目的,需要调用 Field、Method 或 Constructor 对象的 setAccessible 方法。

    setAccessible方法是 AccessibleObject类中的一个方法,它是 Field、 Method 和 Constructor类的公共超类。这个特性是为调试、持久存储和相似机制提供的。

    可以获得就可以设置。调用 f.set(obj,value) 可以将 obj 对象的 f 域设置成新值。

    编写一个可供任意类使用的通用 toString方法:使用getDeclaredFileds 获得所有的数据域,然后使用setAccessible 将所有的域设置为可访问的。对 于每个域,获得了名字和值。

    PS:循环引用将有可能导致无限递归。因此,ObjectAnalyzer将记录已经被访问过的对象。

    为了能够査看数组内部,需要采用一种不同的方式。

    可以使用 toString方法查看任意对象的内部信息。例如,下面这个调用:

    ArrayList<Integer> squares = new ArrayList<>(); 
    for (int i = 1; i <= 5; i++) squares.add(i * i); System.out.println(new ObjectAnalyzer().toString(squares));
    

    将会产生下时的打印结果:

    java.util.Arraylist[elementData=class java.lang.Object[]{java.Intager[value=1][][],java.1ang.Integer[value=4][][],java.1ang.Integer[value=9][][],java.1ang.Integer[value=16][][],java.1ang.Integer[value=25][][],null,null,null,null,null},size=5][modCount=5][][]
    

    还可以使用通用的 toString 方法实现自己类中的 toString 方法, 如下所示:

    public String toString() {
    	return new ObjectAnalyzer().toString(this); 
    }
    

    这是一种公认的提供 toString 方法的手段。

    使用反射编写泛型数组代码

    java.lang.reflect 包中的 Array类允许动态地创建数组。例如,将这个特性应用到 Array类中的 copyOf方法实现中。

    将一个 Employee[]临时地转换成 Object[]数组,然后再把它转换回来是可以的,但一从开始就是 Object[] 的数组却永远不能转换成 Employee[]数组。

    Array类中的静态方法 newlnstance, 它能够构造新数组。在调用它时必须提供两个参数,一个是数组的元素类型,一个是数组的长度。
    Object newArray = Array.newlnstance(componentType, newLength) ;
    可以通过调用 Array.getLength(a) 获得数组的长度。

    而要获得新数组元素类型,就需要进行以下工作:

    1. 首先获得 a数组的类对象。
    2. 确认它是一个数组。
    3. 使用 Class类(只能定义表示数组的类对象)的 getComponentType确定数组对应的类型。
    public static Object goodCopyOf(Object a, int newLength) {
        Class cl = a.getClass(); 
        if (!cl.isArray()) return null; 
        Class componentType = cl.getComponentType(); 
        int length = Array.getLength(a);
        Object newArray = Array.newInstance(componentType, newLength);
        System.arraycopy(a, 0, Math.min(length,newLength));
        return newArray;
    }
    

    请注意,这个 CopyOf方法可以用来扩展任意类型的数组,而不仅是对象数组,基本类型数组也可。

    intn a = { 1,2, 3, 4, 5 };
    a = (int[]) goodCopyOf(a, 10);
    

    整型数组类型 int[] 可以被转换成 Object,但不能转换成对象数组,所以goodCopyOf的参数声明为 Object 类型,而不要声明为对象型数组(Object[])。

    java.lang.reflect.Array 1.1

    • static Object get(Object array,int index)
    • static xxx getxxx(Object array,int index)
      (xxx 是 boolean、byte、char、 double、 float、int、 long、 short 之中的一种基本类型。)
      这些方法将返回存储在给定位置上的给定数组的内容。
    • static void set(Object array,int index,Object newValue)
    • static setXxx(Object array,int index,xxx newValue)
      ( xxx 是 boolean、 byte、char、double、float、int、 long、short 之中的一种基本类型。)
      这些方法将一个新值存储到给定位置上的给定数组中。
    • static int getLength(Object array)
      返回数组的长度。
    • static Object newInstance(Class componentType,int length)
    • static Object newInstance(Class componentType, int[] lengths)
      返回一个具有给定类型、给定维数的新数组

    调用任意方法

    在 Method类中有一个 invoke 方法,它允许调用包装在当前 Method 对象中 的方法。invoke 方法的签名是:
    Object invoke(Object obj, Object... args)
    第一个参数是隐式参数,其余的对象提供了显式参数。

    对于静态方法,第一个参数可以被忽略,即可以将它设置为 null。

    例如,假设用 ml 代表 Employee类的 getName方法,下面这条语句显示了如何调用这个方法:
    String n = (String) ml.invoke(harry);

    如果返回类型是基本类型, invoke方法会返回其包装器类型。

    如何得到 Method对象呢?

    • 可以通过调用 getDeclareMethods方法,然后对返回的 Method对象数组进行查找,直到发现想要的方法为止。

    • 也可以通过调用Class类中的 getMethod方法得到想要的方法。它与 getField方法类似。需要传入方法名和参数类型。
      Method getMethod(String name, Class... parameterTypes)
      例如,下面说明了如何获得 Employee类的 getName方法和 raiseSalary方法的方法指针。

      Method ml = Employee.class.getMethod("getName"); 
      Method m2 = Employee.class.getMethod("raiseSalary",double.class);
      
  • 相关阅读:
    mybatis 调用mysql存储过程 带输出输入参数
    MyBatis 通过包含的jdbcType类型和java中对应的数据类型
    大型网站架构之分布式消息队列
    加密解密(2)*客户端,服务器,CA(Certificate Authority),公钥,私钥,证书,签名,验证
    AlertDialog与DialogFragment
    从日期对象中获取(年份、月份、日期、时间等数字)
    LayoutInflater的获取方式
    php——会话控制
    什么是虚假唤醒 spurious wakeup
    Linux互斥锁、条件变量和信号量
  • 原文地址:https://www.cnblogs.com/l999q/p/12333740.html
Copyright © 2020-2023  润新知