Java反射

来源:互联网 发布:中国 改革开放 知乎 编辑:程序博客网 时间:2024/05/16 14:04

反射的定义:能够分析类能力的程序称为反射。
一、Class类
在程序运行期间,Java运行时系统始终为所有对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行类型信息选择相应的方法执行。

保存这些信息的类被称为Class,Object类的getClass()方法将会返回一个Class类的实例。
例:

Employee e;Class c1=e.getClass();

注:如果一个类在一个包中,包名也作为类名的一部分。

Date d=new Date();Class c1=d.getClass();String name=c1.getName();//name is set to"java.util.Date"

还可以调用静态方法forName获得类名对应的Class对象。
注:这个方法只有在className是类名或接口名的时候才能够执行。否则forName方法将抛出一个checkedException(已检查异常)。无论何时使用这个方法,都应该提供一个异常处理器(exceptionhandler)。

如果T是任意的Java类型,T.class将代表匹配的类对象。

Class c1=Date.class;Class c2=int.class;Class c3=Double[].class;

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

虚拟机为每个类型管理一个Class对象。因此可以使用==运算符实现两个对象比较的操作。

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

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

二、捕获异常
异常处理器(handler)主要用于对异常情况进行处理。如果没有提供处理器程序将要终止。异常有两种类型未检查异常已检查异常。对于已检查异常,编译器会检查是否提供了处理器。应当精心编写代码来避免这些错误的发生,而不要将精力花在编写异常处理器上。对于已检查的异常只需要提供一个异常处理器。

try{    statements that might throw exceptions;}catch(Exception e){    handler action;}

三、利用反射分析类的能力
java.lang.reflect包中有三个类Field、Method和Constructor分别用于描述类的域、方法和构造器。这三个类都有一个叫做getName的方法,用来返回项目的名称。Field类有一个getType方法,用来返回描述域所属类型的Class对象。Method和Constructor类能够报告参数类型的方法,Method类还有一个可以报告返回类型的方法。这三个类还有一个叫做getModifiers的方法,它将返回一个整形数值,用不同的位开关描述publuic和static这样的修饰符使用情况。

Class类中的getFields、getMethods和getConstructors方法将分别返回类提供的public域、方法和构造器数组。

四、在运行时使用反射分析对象
查看对象域的关键方法是Field类的get方法。如果f是一个Field类型的对象(例如,通过getDeclaredFields得到的对象),obj是某个包含f域的类的对象,f.get(obj)将返回一个对象。

Employee harry=new Employee("Harry Hacker",35000,10,1,1989);Class c1=harry.getClass();Field f=c1.getDeclaredField("name");Object v=f.get(harry);

实际上此段代码存在一个问题,name是一个私有域,所以get方法将会抛出一个IllegalAccessException。只有利用get方法才能得到可访问域的值。除非拥有访问权限,否则Java安全机制只允许查看任意对象有那些域,而不允许读取它们的值。

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

f.setAccessible(true);//now Ok to call f.get(harry);

setAccessible方法是AccessibleObject类中的一个方法,它是Field、Method和Constructor类的公共超类。

调用f.set(obj,value)可以将obj对象的f域设为新值。

几个重要的方法:

void setAccessible(boolean flag);//为反射对象设置可访问标志。flag为true表示屏蔽Java语言的访问检查,使得对象的私有属性也可以被查询和设置。boolean isAccessible();//返回反射对象的可访问标志的值。static void setAccessible(AccessibleObject[] array,boolean flag);//是一种设置对象数组可访问标志的快捷方法。Field[] getField();//返回指定名称的公有域,或包含所有域的数组。Field[] getDeclaredFields();//返回类中声明的给定名称的域,或者包含声明的全部域的数组。Object get(Object obj);//返回obj对象中用Field对象表示的域值。void set(Object obj,Object newValue);//用一个新值设置obj对象中Field对象表示的域。

五、使用反射编写泛型数组编码
newInstance能够构造新的数组。在调用它时必须提供两个参数,一个是数组的元素类型,一个是数组的长度。

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

(1)调用Array.getLength(a)获得数组的长度。
(2)使用Class类的getComponentType方法确定数组对应的类型。

六、调用任意方法
在Method类中有一个invoke方法,它允许调用包装在当前Method对象中的方法。invoke方法的签名是

Object invoke(Object obj,Object...args);

第一个参数是隐式参数,其余的对象提供了显式参数。对于静态方法第一个参数可以省略,即可以将它设置为null。
例如m1代表的是Employee类的getName方法,可以使用以下语句调用这个方法。

String n=(String)m1.invoke(harry);

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

public static void printTable(double from,double to,int n,Method f){    double dx=(to-from)/(n-1);    for(double x=from;x<=to;x++)    {        try        {            double y=(Double)f.invoke(null.x);            System.out.printf("%10.4f|%10.4f%n",x,y);        }        catch(Exception e)        {            e.printStackTrace();        }    }}

注:使用反射方法获得的方法指针要比直接调用的方法慢一些。因此,只有在必要的时候才能使用Method对象,最好使用接口或者是内部类。使用接口回调会使得代码执行速度更快更易于维护。

public Object invoke(Object implicitParameter,Object[]explicitParameters)//调用这个对象所描述的方法,传递给定的参数,并返回方法的返回值。对于静态方法把null作为隐式参数传递。在调用包装器传递基本类型值时,基本类型的返回值必须是未包装的。
原创粉丝点击