java反射机制详解.docx

上传人:b****7 文档编号:16269446 上传时间:2023-07-12 格式:DOCX 页数:14 大小:22.90KB
下载 相关 举报
java反射机制详解.docx_第1页
第1页 / 共14页
java反射机制详解.docx_第2页
第2页 / 共14页
java反射机制详解.docx_第3页
第3页 / 共14页
java反射机制详解.docx_第4页
第4页 / 共14页
java反射机制详解.docx_第5页
第5页 / 共14页
java反射机制详解.docx_第6页
第6页 / 共14页
java反射机制详解.docx_第7页
第7页 / 共14页
java反射机制详解.docx_第8页
第8页 / 共14页
java反射机制详解.docx_第9页
第9页 / 共14页
java反射机制详解.docx_第10页
第10页 / 共14页
java反射机制详解.docx_第11页
第11页 / 共14页
java反射机制详解.docx_第12页
第12页 / 共14页
java反射机制详解.docx_第13页
第13页 / 共14页
java反射机制详解.docx_第14页
第14页 / 共14页
亲,该文档总共14页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

java反射机制详解.docx

《java反射机制详解.docx》由会员分享,可在线阅读,更多相关《java反射机制详解.docx(14页珍藏版)》请在冰点文库上搜索。

java反射机制详解.docx

java反射机制详解

java-反射机制txt.txt25爱是一盏灯,黑暗中照亮前行的远方;爱是一首诗,冰冷中温暖渴求的心房;爱是夏日的风,是冬日的阳,是春日的雨,是秋日的果。

反射使您的程序代码能够接入装载到JVM中的类的内部信息,允许您编写与执行时,而不是源代码中选定的类协作的代码。

这使反射成为构建灵活的应用的主要工具。

但需注意的是--如果使用不当,反射的成本很高。

在Java平台系列的第2部分中,软件顾问DennisSosnoski介绍了如何使用反射,以及某些相关的成本。

您还将找到JavaReflectionAPI如何使您能够在运行时关联对象。

在“Java编程的动态性,第1部分,”我为您介绍了Java编程类和类装入。

该篇文章介绍了一些Java二进制类格式的相关信息。

这个月我将阐述使用Java反射API来在运行时接入和使用一些相同信息的基础。

为了使已经熟知反射基础的开发人员关注本文,我将在文章中包括反射性能如何与直接接入相比较。

使用反射不同于常规的Java编程,其中它与元数据--描述其它数据的数据协作。

Java语言反射接入的特殊类型的原数据是JVM中类和对象的描述。

反射使您能够运行时接入广泛的类信息。

它甚至使您能够读写字段,调用运行时选择的类的方法。

反射是一种强大的工具。

它使您能够创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代表链接。

但反射的某些方面存在一些疑问。

在本文中,我将深入讨论为什么您可能不希望在程序中使用反射,以及您应该这样做的理由。

在了解了权衡性分析之后,您可以自行决定是否利大于弊。

初学者的类

使用反射的启点总是java.lang.Class实例。

如果您希望与预先定义的类协作,那么Java语言提供一种直接获得Class实例的简便快捷方式:

第1部分,“类和类装入”

代码:

Classclas=MyClass.class;

当您使用这一项技术时,装入类涉及的所有工作在幕后进行。

但是,如果您需要在运行时从某些外部源读取类名,这种方法并不适合。

实际上,您需要使用一个类装入器来查找类信息。

以下介绍一种方法:

代码:

//"name"istheclassnametoload

Classclas=null;

try{

clas=Class.forName(name);

}catch(ClassNotFoundExceptionex){

//handleexceptioncase

}

//usetheloadedclass

如果已经装入了类,您将得到现有的Class信息。

如果类未被装入,类装入器将现在装入并返回新创建的类实例。

基于类的反射

Class对象为您提供接入类元数据的反射的所有基本hook。

这类元数据包括关于类自身的信息,如包和类的父类,以及该类实施的接口。

它还包括该类定义的构造函数、字段和方法的详细信息。

这些最后的项目都是编程中最经常使用的项目,因此我将在本小节的稍后部分给出一些与它们协作的实例。

对于以下三类组件中的任何一类来说--构造函数、字段和方法--java.lang.Class提供四种独立的反射调用,以不同的方式来获得信息。

调用都遵循一种标准格式。

以下是用于查找构造函数的一组反射调用:

ConstructorgetConstructor(Class[]params)--获得使用特殊的参数类型的公共构造函数,

Constructor[]getConstructors()--获得类的所有公共构造函数

ConstructorgetDeclaredConstructor(Class[]params)--获得使用特定参数类型的构造函数(与接入级别无关)

Constructor[]getDeclaredConstructors()--获得类的所有构造函数(与接入级别无关)

每类这些调用都返回一个或多个java.lang.reflect.Constructor函数。

这种Constructor类定义newInstance方法,它采用一组对象作为其唯一的参数,然后返回新创建的原始类实例。

该组对象是用于构造函数调用的参数值。

作为解释这一工作流程的实例,假设您有一个TwoString类和一个使用一对Strings的构造函数,如清单1所示:

清单1:

从一对字符串创建的类

代码:

publicclassTwoString{

privateStringm_s1,m_s2;

publicTwoString(Strings1,Strings2){

m_s1=s1;

m_s2=s2;

}

}

清单2中的代码获得构造函数并使用它来创建使用Strings"a"和"b"的TwoString类的一个实例:

清单2:

构造函数的反射调用

代码:

Class[]types=newClass[]{String.class,String.class};

Constructorcons=TwoString.class.getConstructor(types);

Object[]args=newObject[]{"a","b"};

TwoStringts=cons.newInstance(args);

清单2中的代码忽略了不同反射方法抛出的多种可能选中的例外类型。

例外在JavadocAPI描述中详细记录,因此为了简明起见,我将在所有程序实例中忽略它们。

尽管我在讨论构造函数主题,Java编程语言还定义了一种您可以用来使用无参数(或缺省)构造函数创建类的一个实例的特殊快捷方式。

这种快捷方式嵌入到Class定义中,如下:

ObjectnewInstance()--使用缺省函数创建新的实例

即使这种方法只允许您使用一种特殊的构造函数,如果这正是您需要的,那么它将提供一种非常方便的快捷方式。

当与JavaBeans协作时这项技术尤其有用,JavaBeans需要定义公共、无参数构造函数。

通过反射增加字段获得字段信息的Class反射调用不同于那些用于接入构造函数的调用,在参数类型数组中使用了字段名:

FieldgetField(Stringname)--获得命名的公共字段

Field[]getFields()--获得类的所有公共字段

FieldgetDeclaredField(Stringname)--获得类声明的命名的字段

Field[]getDeclaredFields()--获得类声明的所有字段

本文来自:

()详细出处参考:

尽管与构造函数调用类似,在字段方面仍存在一个重要的区别:

前两个变量返回可以通过类接入的公共字段的信息--即使它们来自于祖先类。

后两个变量返回类直接声明的字段的信息--与字段的接入类型无关。

调用返回的java.lang.reflect.Field实例定义所有主类型的getXXX和setXXX方法,以及与对象引用协作的通用get和set方法。

您可以根据实际的字段类型自行选择一种适当的方法,而getXXX方法将自动处理扩展转换(如使用getInt方法来检索一个字节值)。

清单3显示使用字段反射方法的一个实例,以方法的格式根据名称增加对象的int字段:

清单3:

通过反射增加一个字段

代码:

publicintincrementField(Stringname,Objectobj)throws...{

Fieldfield=obj.getClass().getDeclaredField(name);

intvalue=field.getInt(obj)+1;

field.setInt(obj,value);

returnvalue;

}

这种方法开始展示了反射带来的某些灵活性。

与特定的类协作不同,incrementField使用传入的对象的getClass方法来查找类信息,然后直接在该类中查找命名的字段。

通过反射增加方法

获得方法信息的Class反射调用与用于构造函数和字段的调用非常类似:

MethodgetMethod(Stringname,Class[]params)--使用特定的参数类型,获得命名的公共方法

Method[]getMethods()--获得类的所有公共方法

MethodgetDeclaredMethod(Stringname,Class[]params)--使用特写的参数类型,获得类声明的命名的方法

Method[]getDeclaredMethods()--获得类声明的所有方法

与字段调用一样,前两个变量返回可以通过类接入的公共方法的信息--即使它们来自于祖先类。

后两个变量返回类声明的方法的信息,与方法的接入类型无关。

调用返回的java.lang.reflect.Method实例定义一种invoke方法,您可以用来在正在定义的类的一个实例上调用方法。

这种invoke方法使用两个参数,为调用提供类实例和参数值数组。

清单4进一步阐述字段实例,显示反射正在运行的方法的一个实例。

这种方法增加一个定义有get和set方法的intJavaBean属性。

例如,如果对象为一个整数count值定义了getCount和setCount方法,您可以在一次调用中向该方法传递“count”作为name参数,以增加该值。

清单4:

通过反射增加一个JavaBean属性

代码:

publicintincrementProperty(Stringname,Objectobj){

Stringprop=Character.toUpperCase(name.charAt(0))+

name.substring

(1);

Stringmname="get"+prop;

Class[]types=newClass[]{};

Methodmethod=obj.getClass().getMethod(mname,types);

Objectresult=method.invoke(obj,newObject[0]);

intvalue=((Integer)result).intValue()+1;

mname="set"+prop;

types=newClass[]{int.class};

method=obj.getClass().getMethod(mname,types);

method.invoke(obj,newObject[]{newInteger(value)});

returnvalue;

}

为了遵循JavaBeans惯例,我把属性名的首字母改为大写,然后预先考虑get来创建读方法名,set来创建写方法名。

JavaBeans读方法仅返回值,而写方法使用值作为唯一的参数,因此我规定方法的参数类型以进行匹配。

最后,该惯例要求方法为公共,因此我使用查找格式,查找类上可调用的公共方法。

这一实例是第一个我使用反射传递主值的实例,因此现在我们来看看它是如何工作的。

基本原理很简单:

无论什么时候您需要传递主值,只需用相应封装类的一个实例(在java.lang包中定义)来替换该类主值。

这可以应用于调用和返回。

因此,当我在实例中调用get方法时,我预计结果为实际int属性值的java.lang.Integer封装。

反射数组

数组是Java编程语言中的对象。

与所有对象一样,它们都有类。

如果您有一个数组,使用标准getClass方法,您可以获得该数组的类,就象任何其它对象一样。

但是,不通过现有的实例来获得类不同于其它类型的对象。

即使您有一个数组类,您也不能直接对它进行太多的操作--反射为标准类提供的构造函数接入不能用于数组,而且数组没有任何可接入的字段,只有基本的java.lang.Object方法定义用于数组对象。

数组的特殊处理使用java.lang.reflect.Array类提供的静态方法的集合。

该类中的方法使您能够创建新数组,获得数组对象的长度,读和写数组对象的索引值。

清单5显示了一种重新调整现有数组大小的有效方法。

它使用反射来创建相同类型的新数组,然后在返回新数组之前,在老数组中复制所有数据。

清单5:

通过反射来扩展一个数组

代码:

publicObjectgrowArray(Objectarray,intsize){

Classtype=array.getClass().getComponentType();

Objectgrown=Array.newInstance(type,size);

System.arraycopy(array,0,grown,0,

Math.min(Array.getLength(array),size));

returngrown;

}

安全性和反射

在处理反射时安全性是一个较复杂的问题。

反射经常由框架型代码使用,由于这一点,您可能希望框架能够全面接入您的代码,无需考虑常规的接入限制。

但是,在其它情况下,不受控制的接入会带来严重的安全性风险,如当代码在不值得信任的代码共享的环境中运行时。

由于这些互相矛盾的需求,Java编程语言定义一种多级别方法来处理反射的安全性。

基本模式是对反射实施与应用于源代码接入相同的的限制:

从任意位置到类公共组件的接入

类自身外部无任何到私有组件的接入

受保护和打包(缺省接入)组件的有限接入

不过-至少某些时候,围绕这些限制有一种简单的方法。

我在前面实例中使用的Constructor、Field和Method类都扩展了一个普通的基本类--java.lang.reflect.AccessibleObject类。

该类定义一种setAccessible方法,使您能够启动或关闭对这些类中其中一个类的实例的接入检测。

唯一的问题在于如果使用了安全性管理器,它将检测正在关闭接入检测的代码是否许可了这样做。

如果未许可,安全性管理器抛出一个例外。

清单6展示了一个程序,在清单1TwoString类的一个实例上使用反射来显示安全性正在运行:

清单6:

反射安全性正在运行

代码:

publicclassReflectSecurity{

publicstaticvoidmain(String[]args){

try{

TwoStringts=newTwoString("a","b");

Fieldfield=clas.getDeclaredField("m_s1");

//field.setAccessible(true);

System.out.println("Retrievedvalueis"+

field.get(inst));

}catch(Exceptionex){

ex.printStackTrace(System.out);

}

}

}

如果您编译了这一程序,不使用任何特定参数直接从命令行运行,它将在field.get(inst)调用中抛出一个IllegalAccessException。

如果您未注释field.setAccessible(true)代码行,那么重新编译并重新运行该代码,它将取得成功。

最后,如果您在命令行添加了JVM参数-Djava.security.manager以实现安全性管理器,它将再次失败,除非您定义了ReflectSecurity类的许可权限。

反射性能

反射是一种强大的工具,但也存在一些不足。

一个主要的缺点是对性能有影响。

使用反射基本上是一种解释操作,您可以告诉JVM您希望做什么并且它满足您的要求。

这类操作总是慢于只直接执行相同的操作。

为了阐述使用反射的性能成本,我为本文准备了一组基准程序(见参考资料,完整代码链接)。

清单7是字段接入性能测试的一个摘用,包括基本的测试方法。

每种方法测试字段接入的一种形式--accessSame与同一对象的成员字段协作,accessOther使用可直接接入的另一对象的字段,accessReflection使用可通过反射接入的另一对象的字段。

在每种情况下,方法执行相同的计算--循环中简单的加/乘顺序。

清单7:

字段接入性能测试代码

代码:

publicintaccessSame(intloops){

m_value=0;

for(intindex=0;index

m_value=(m_value+ADDITIVE_VALUE)*

MULTIPLIER_VALUE;

}

returnm_value;

}

publicintaccessReference(intloops){

TimingClasstiming=newTimingClass();

for(intindex=0;index

timing.m_value=(timing.m_value+ADDITIVE_VALUE)*

MULTIPLIER_VALUE;

}

returntiming.m_value;

}

publicintaccessReflection(intloops)throwsException{

TimingClasstiming=newTimingClass();

try{

Fieldfield=TimingClass.class.

getDeclaredField("m_value");

for(intindex=0;index

intvalue=(field.getInt(timing)+

ADDITIVE_VALUE)*MULTIPLIER_VALUE;

field.setInt(timing,value);

}

returntiming.m_value;

}catch(Exceptionex){

System.out.println("Errorusingreflection");

throwex;

}

}

测试程序重复调用每种方法,使用一个大循环数,从而平均多次调用的时间衡量结果。

平均值中不包括每种方法第一次调用的时间,因此初始化时间不是结果中的一个因素。

在为本文进行的测试中,每次调用时我使用1000万的循环数,在1GHzPIIIm系统上运行。

三个不同LinuxJVM的计时结果如图1所示。

所有测试使用每个JVM的缺省设置。

本文来自:

()详细出处参考:

上表的对数尺度可以显示所有时间,但减少了差异看得见的影响。

在前两副图中(SunJVM),使用反射的执行时间超过使用直接接入的1000倍以上。

通过比较,IBMJVM可能稍好一些,但反射方法仍旧需要比其它方法长700倍以上的时间。

任何JVM上其它两种方法之间时间方面无任何显著差异,但IBMJVM几乎比SunJVM快一倍。

最有可能的是这种差异反映了SunHotSpotJVM的专业优化,它在简单基准方面表现得很糟糕。

除了字段接入时间测试之外,我还进行了相同的方法调用时间测试。

在方法调用中,我试用了与字段接入相同的三种接入变量,并增加了使用无参数方法变量,而不是在方法调用中传递和返回一个值。

清单8显示了用于测试调用传递和返回值形式的三种方法的代码。

清单8:

方法接入性能测试代码

代码:

publicintcallDirectArgs(intloops){

intvalue=0;

for(intindex=0;index

value=step(value);

}

returnvalue;

}

publicintcallReferenceArgs(intloops){

TimingClasstiming=newTimingClass();

intvalue=0;

for(intindex=0;index

value=timing.step(value);

}

returnvalue;

}

publicintcallReflectArgs(intloops)throwsException{

TimingClasstiming=newTimingClass();

try{

Methodmethod=TimingClass.class.getMethod

("step",newClass[]{int.class});

Object[]args=newObject[1];

Objectvalue=newInteger(0);

for(intindex=0;index

args[0]=value;

value=method.invoke(timing,args);

}

return((Integer)value).intValue();

}catch(Exceptionex){

System.out.println("Errorusingreflection");

throwex;

}

}

反射性能是Sun开发1.4JVM时关注的一个方面,它在反射方法调用结果中显示。

在这类操作的性能方面,Sun1.4.1JVM显示了比1.3.1版本很大的改进,在我的测试中运行速度大约是1.3.1版本的开部。

在这类简单的测试中,IBM1.4.0JVM再次获得了更好的成绩,但是只比Sun1.4.1JVM快两到三倍。

我还为创建使用反射的对象编写了类似的计时测试程序,但这种情况下的差异不象字段和方法调用情况下那么显著。

使用newInstance()调用创建一个简单的java.lang.Object实例耗用的时间大约是在Sun1.3.1JVM上使用newObject()的12倍

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 人文社科 > 法律资料

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2