java 的反射技术

来源:互联网 发布:数据云平台 编辑:程序博客网 时间:2024/05/16 02:04

1.反射机制概述
      Reflection是Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说"自审",并能直接操作程序的内部属性。例如,使用它能获得 Java 类中各成员的名称并显示出来。这种动态获取类的信息以及动态调用对象的方法的功能来自于Java 语言的反射(Reflection)机制。

Java 反射机制主要提供了以下功能:
     在运行时判断任意一个对象所属的类。
     在运行时构造任意一个类的对象。
     在运行时判断任意一个类所具有的成员变量和方法。
     在运行时调用任意一个对象的方法。

在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中:
     Class类:代表一个类。
     Field 类:代表类的成员变量(成员变量也称为类的属性)。
     Method类:代表类的方法。
     Constructor 类:代表类的构造方法。
     Array类:提供了动态创建数组,以及访问数组的元素的静态方法。[1]

2.动态语言和动态性
Reflection 是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static等等)、superclass(例如Object)、实现之interfaces(例如Serializable),也包括fields和methods 的所有信息,并可于运行时改变fields内容或调用methods。
 
动态语言大致认同的一个定义是:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。[1]

3.Class------反射的基石

       Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标识。这项信息纪录了每个对象所属的类。虚拟机通常使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的类是Class类。Class类封装一个对象和接口运行时的状态,当装载类时,Class类型的对象自动创建。
      Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。
      虚拟机为每种类型管理一个独一无二的Class对象。也就是说,每个类(型)都有一个Class对象。运行程序时,Java虚拟机(JVM)首先检查是否所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。
      基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。
      每个数组属于如果(“属于”可以去掉)被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
      一般某个类的Class对象被载入内存,它就用来创建这个类的所有对象。[2]

获得Class的对象有三种方法:
    1、调用Object类的getClass()方法来得到Class对象,这也是最常见的产生Class对象的方法。例如:
         MyObject x;
         Class c1 = x.getClass();
    2、使用Class类的中静态forName()方法获得与字符串对应的Class对象。例如:
         Class c2=Class.forName("MyObject");
         MyObject必须是接口或者类的名字。这种方法在反射中用的最多,因为可用字符串型变量代替括号中的内容,在程序运行中动态的给括号中的变量赋值。


    3、获取Class类型对象的第三个方法非常简单。如果T是一个Java类型,那么T.class就代表了匹配的类对象。例如
         Class cl1 = Manager.class;
         Class cl2 = int.class;
         Class cl3 = Double[].class;
    注意:Class对象实际上描述的只是类型,而这类型未必是类或者接口。例如上面的int.class是一个Class类型的对象。由于历史原因,数组类型的getName方法会返回奇怪的名字。 

java.lang.Class类是Reflection API 中的核心类,它有以下方法

    getName():获得类的完整名字。
    getClassLoader():返回该类的类加载器。 
    getComponentType():返回表示数组组件类型的 Class。

    newInstance():通过类的不带参数的构造方法创建这个类的一个对象。

        x.getClass.newInstance(),创建了一个同x一样类型的新实例。newInstance()方法调用默认构造器(无参数构造器)初始化新建对象。 
    getSuperclass():返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。 
    isArray():判定此 Class 对象是否表示一个数组类。 

    与属性相关函数
    getfield(string name):field
    getfields():field[]:获得某个类的所有的公共(public)的字段,包括父类。
    getdeclaredfield(string name):field
    getdeclaredfields():field[]:获得某个类的所有申明的字段,即包括public、private和proteced,但是不包括父类的申明字段。 

    与方法相关的函数
    getmethod(string name,class... parametertypes):method
    getmethods():method[]:获得类的public类型的方法,包括父类。
    getdeclaredmethods():method[]:获得某个类的所有申明的字段,即包括public、private和proteced,但是不包括父类的申明字段。 
    getdeclaredmethod(string name,class ...parametertypes):method:获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。


    与构造函数相关函数

    getConstructors():获得类的public类型的构造方法。

    getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
    构造函数不能继承,因此你调用getconstructor也只能返回这个类型中定义的所有公有构造函数。


1getName()举例
        Class<?> c = Class.forName("java.lang.String");
        String name = c.getName();
        System.out.println(name);
参见:TestClasForname.java
结果:java.lang.String


2:获取类的属性举例
[3]
    public class A {
        public String a1="a1";
        private String a2="a2";
    }
    public class B extends A {
        public String b1="b1";
        private String b2="b2";
    }
如果利用A的类型类调用getfields,那么会返回a1属性,代码如下:
            Class<?> c = Class.forName("A");
            Field[] fieldNames = c.getFields();
            for(int i=0;i<fieldNames.length;i++)
                System.out.println(fieldNames[i].toString());
结果:
           public java.lang.String A.a1

如果利用B的类型类调用getfields,那么会返回a1和b1属性,代码如下:
            Class<?> c = Class.forName("B");
            Field[] fieldNames = c.getFields();
            for(int i=0;i<fieldNames.length;i++)
                System.out.println(fieldNames[i].toString());
结果:
           public java.lang.String B.b1
           public java.lang.String A.a1

根据属性名称获取Field
           c = Class.forName("A");
           Field f = c.getField("a1");  //返回public java.lang.String A.a1
           Field f = c.getField("a2");  //出错
           Field f = c.getDeclaredField("a2");  //返回private java.lang.String A.a2

要想获得private类型的变量可见Field 1.
参见:TestGetFields.java

3:获取类的方法举例
public class A {
    public String a1="a1";
    private String a2="a2";
    public void methodA1(){
    }
    private void methodA2(){
    }
}
public class B extends A {
     public String b1="b1";
     private String b2="b2";
     public void methodB1(){
     }
     private void methodB2(){
     }
}
通过getDeclaredMethods(),获取当前类所有方法
            Class<?> c = Class.forName("A");
            Method[] array_method = c.getDeclaredMethods();
            for(int i=0;i<array_method.length;i++)
                System.out.println(array_method[i].toString());
结果
      public void A.methodA1()
      private void A.methodA2()

通过getMethods(),获取当前类所有方法(包括父类)
            c = Class.forName("A");
            array_method = c.getDeclaredMethods();
            for(int i=0;i<array_method.length;i++)
                System.out.println(array_method[i].toString());
结果
      public void A.methodA1()
      public final void java.lang.Object.wait() throws java.lang.InterruptedException
      public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException

通过指定的函数名,获取方法(不带参数)
            c = Class.forName("B");
            Class[] argsClass = new Class[]{};   
            Method m = c.getMethod("methodB1", argsClass );
            或 Method m = c.getMethod("methodB1", new Class[]{});
            System.out.println(m.toString());
结果
      public void B.methodB1()

通过指定的函数名,获取方法(带基本类型参数)
            c = Class.forName("B");
            Class[] parameterTypes = new Class[] { int.class, int.class };
            Method myMethod = c.getMethod("add", parameterTypes);
            System.out.println(myMethod.toString());
结果
       public int B.add(int,int)

通过指定的函数名,获取方法(参数为数组)
           c = Class.forName("B");
            parameterTypes = new Class[] {int[].class};
            myMethod = c.getMethod("testArray", parameterTypes);
            System.out.println(myMethod.toString());
结果
       public int B.testArray(int[])
参见:TestGetMothed.java

4:获取构造函数
         Class<?> c = Class.forName("A");
         Constructor[] constructors = c.getConstructors();  
         for(int i=0;i<constructors.length;i++)
                System.out.println(constructors[i].toString());
结果
      public A()
      public A(java.lang.String)
参见:TestConstructor.java

4.Field
     gettype():class:获得该属性的类型。
     getname():string:获得属性名称。
     isaccessible():boolean:判断该属性是否是可以访问的,通常私有和保护的类型都是不可以访问的。
     get(object obj):object:获得实例obj的属性值,如果这个属性是非公有的,这里会报IllegalAcces***ception。
     set(objectobj,objectvalue):设置该实例的属性值
     setAccessible(booleanflag):设置该属性是否可以访问,如果你调用get和set方法,那么有可能会引发访问权限的错误,这个时候你可以调用setaccessible方法使得该属性可以访问。

1:将private属性a2设置为可访问,并修改其值
        A a=new A();
        Field f=A.class.getDeclaredField("a2");
        f.setAccessible(true);
        System.out.println(f.get(a));
        
        f.set(a,"123456");
        System.out.println(f.get(a));
结果
        a2
        123456
参见:TestField.java

5.Method

      getname():string:获得方法的名字。
      getreturntype():class:获得方法的返回值类型。
      getparametertypes():class[]:获得方法的参数类型。
      isaccessible():boolean:判断该方法是否是可以访问的。
      setaccessible(booleanflag):设置该方法是否可以访问。
      getexceptiontypes():class[]:获得该方法可能抛出的异常类类型。
      invoke(objectobj,object...args):object:调用实例obj的相应方法,其参数由args给定,如果没有参数那么可以什么都不写。

1:通过invoke调用指定的函数
        public int add(int param1, int param2)
        {
             return param1 + param2;
        }
        Method addMethod = classType.getMethod("add", new Class[] { int.class, int.class });
        Object result = addMethod.invoke(invokeTester, new Object[] { new Integer(100), new Integer(200) });
        System.out.println((Integer) result);


参见:TestInvoke.java

import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class TestInvoke {public int add(int a, int b) {return a + b;}public int sub(int a, int b) {return a - b;}public int mul(int a, int b) {return a * b;}public int div(int a, int b) {return a / b;}public static void main(String args[]) {Integer[] input = { new Integer(2), new Integer(3) };// 方法中所需要的参数Class cl = null;try {cl = Class.forName("TestInvoke");} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}// 获取TestInvoke的ClassMethod method = null;try {method = cl.getMethod("add", new Class[] { int.class, int.class });} catch (NoSuchMethodException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (SecurityException e) {// TODO Auto-generated catch blocke.printStackTrace();}// 获取add方法的MethodInteger output = null;try {output = (Integer) method.invoke(new TestInvoke(), input);} catch (IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalArgumentException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (InvocationTargetException e) {// TODO Auto-generated catch blocke.printStackTrace();}// 调用TestInvoke里的add方法,并将input参数传给add方法System.out.println("output=" + output.intValue());// 输出结果}}
invoke方法的调用过程:
一、先生成该类的class
二、获取相应的方法Method
三、对于该方法中所需要的参数,进行构造
四、用相应的method来调用

6.反射技术的优点

Java语言反射提供一种动态链接程序组件的多功能方法。它允许程序创建和控制任何类的对象(根据安全性限制),无需提前编译目标类。这些特性使得反射特别适用于创建以特殊的方式与对象协作的库。例如,反射经常在持续存储对象为数据库、XML或其它外部格式的框架中使用。Java reflection 非常有用,它使类和数据结构能按名称动态检索相关信息,并允许在运行着的程序中操作这些信息。

 

7.反射技术的缺点

用于字段和方法接入时反射要远慢于直接代码。

测试用类DemoClass.java见附件

1实例化效率测试

new方法实例化代码

          int i = 0;

        long start = System.currentTimeMillis();

        while(i<1000000){

        i++;

        new DemoClass();

        }

       

        long end = System.currentTimeMillis();

        System.err.println((end - start) + " MillSeconds");

结果:第一次16 MillSeconds

      第二次16 MillSeconds

      第三次16 MillSeconds

使用反射方法实例化

int i = 0;

        long start = System.currentTimeMillis();

        while(i<1000000){

        i++;

        DemoClass.class.newInstance();

        }

       

        long end = System.currentTimeMillis();

        System.err.println((end - start) + " MillSeconds");

}

结果:第一次:203 MillSeconds

  第二次:188 MillSeconds

  第三次:187 MillSeconds

 

方法调用测试

一般方法代码:

int i = 0;

        DemoClass dc = new DemoClass();

        long start = System.currentTimeMillis();

        while(i<1000000){

        i++;

        dc.getBirthDay();

        }

       

        long end = System.currentTimeMillis();

        System.err.println((end - start) + " MillSeconds");

    }

结果:第一次:0 MillSeconds

      第二次:0 MillSeconds

      第三次:0 MillSeconds

 

反射方法: 

int i = 0;

        DemoClass dc = new DemoClass();

        long start = System.currentTimeMillis();

        while(i<1000000){

        i++;

        DemoClass.class.getMethod("getBirthDay").invoke(dc);

 

        }

       

        long end = System.currentTimeMillis();

        System.err.println((end - start) + " MillSeconds");

    }

结果:第一次:1282 MillSeconds

  第二次:1281 MillSeconds

  第三次:1281 MillSeconds

 

3测试成员变量GET

一般方法:

int i = 0;

        DemoClass dc = new DemoClass();

        String s;

        long start = System.currentTimeMillis();

        while(i<1000000){

        i++;

        s = dc.firstName;

        }

       

        long end = System.currentTimeMillis();

        System.err.println((end - start) + " MillSeconds");

    }

结果:第一次:0 MillSeconds

  第二次:0 MillSeconds

  第三次:0 MillSeconds

反射方法:

int i = 0;

        DemoClass dc = new DemoClass();

        String s;

        long start = System.currentTimeMillis();

        while(i<1000000){

        i++;

        s = (String) DemoClass.class.getField("firstName").get(dc);

        }

       

        long end = System.currentTimeMillis();

        System.err.println((end - start) + " MillSeconds");

结果:第一次:1031 MillSeconds

  第二次:1016 MillSeconds

  第三次:1032 MillSeconds

统计

 

反射平均时间

非反射平均时间

比值

实例化

192

16

12

方法调用

1281

0

 

成员Get

1026

0

 

性能问题的程度取决于程序中是如何使用反射的。如果它作为程序运行中相对很少涉及的部分,缓慢的性能将不会是一个问题。即使测试中最坏情况下的计时图显示的反射操作只耗用几微秒。反射在性能关键的应用如核心逻辑中使用时的性能问题才变得至关重要。