对JAVA反射机制的认识Word文件下载.docx
《对JAVA反射机制的认识Word文件下载.docx》由会员分享,可在线阅读,更多相关《对JAVA反射机制的认识Word文件下载.docx(10页珍藏版)》请在冰豆网上搜索。
![对JAVA反射机制的认识Word文件下载.docx](https://file1.bdocx.com/fileroot1/2022-11/25/183f1551-0b82-4d12-a0b2-2711a43f8d9b/183f1551-0b82-4d12-a0b2-2711a43f8d9b1.gif)
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[]{"
"
};
TwoStringts=cons.newInstance(args);
清单2中的代码忽略了不同反射方法抛出的多种可能选中的例外类型。
例外在JavadocAPI描述中详细记录,因此为了简明起见,我将在所有程序实例中忽略它们。
尽管我在讨论构造函数主题,Java编程语言还定义了一种您可以用来使用无参数(或缺省)构造函数创建类的一个实例的特殊快捷方式。
这种快捷方式嵌入到Class定义中,如下:
ObjectnewInstance()--使用缺省函数创建新的实例
即使这种方法只允许您使用一种特殊的构造函数,如果这正是您需要的,那么它将提供一种非常方便的快捷方式。
当与JavaBeans协作时这项技术尤其有用,JavaBeans需要定义公共、无参数构造函数。
2.2.基于字段的反射
字段,可以理解为类的属性。
获得字段信息的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方法来查找类信息,然后直接在该类中查找命名的字段。
2.3.基于方法的反射
获得方法信息的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"
types=newClass[]{int.class};
method=obj.getClass().getMethod(mname,types);
method.invoke(obj,newObject[]{newInteger(value)});
为了遵循JavaBeans惯例,我把属性名的首字母改为大写,然后预先考虑get来创建读方法名,set来创建写方法名。
JavaBeans读方法仅返回值,而写方法使用值作为唯一的参数,因此我规定方法的参数类型以进行匹配。
最后,该惯例要求方法为公共,因此我使用查找格式,查找类上可调用的公共方法。
这一实例是第一个我使用反射传递主值的实例,因此现在我们来看看它是如何工作的。
基本原理很简单:
无论什么时候您需要传递主值,只需用相应封装类的一个实例(在java.lang包中定义)来替换该类主值。
这可以应用于调用和返回。
因此,当我在实例中调用get方法时,我预计结果为实际int属性值的java.lang.Integer封装。
2.4.数组的反射
数组是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;
3.实例中的经典运用
1.得到某个对象的属性
publicObjectgetProperty(Objectowner,StringfieldName)throwsException{
ClassownerClass=owner.getClass();
Fieldfield=ownerClass.getField(fieldName);
Objectproperty=field.get(owner);
returnproperty;
}
ClassownerClass=owner.getClass():
得到该对象的Class。
Fieldfield=ownerClass.getField(fieldName):
通过Class得到类声明的属性。
Objectproperty=field.get(owner):
通过对象得到该属性的实例,如果这个属性是非公有的,这里会报IllegalAccessException。
2.得到某个类的静态属性
publicObjectgetStaticProperty(StringclassName,StringfieldName)
throwsException{
ClassownerClass=Class.forName(className);
Objectproperty=field.get(ownerClass);
ClassownerClass=Class.forName(className):
首先得到这个类的Class。
和上面一样,通过Class得到类声明的属性。
Objectproperty=field.get(ownerClass):
这里和上面有些不同,因为该属性是静态的,所以直接从类的Class里取。
3.执行某对象的方法
publicObjectinvokeMethod(Objectowner,StringmethodName,Object[]args)throwsException{
Class[]argsClass=newClass[args.length];
for(inti=0,j=args.length;
i<
j;
i++){
argsClass[i]=args[i].getClass();
}
Methodmethod=ownerClass.getMethod(methodName,argsClass);
returnmethod.invoke(owner,args);
Classowner_class=owner.getClass():
首先还是必须得到这个对象的Class。
3~6行:
配置参数的Class数组,作为寻找Method的条件。
Methodmethod=ownerClass.getMethod(methodName,argsClass):
通过Method名和参数的Class数组得到要执行的Method。
method.invoke(owner,args):
执行该Method,invoke方法的参数是执行这个方法的对象,和参数数组。
返回值是Object,也既是该方法的返回值。
4.执行某个类的静态方法
publicObjectinvokeStaticMethod(StringclassName,StringmethodName,
Object[]args)throwsException{
returnmethod.invoke(null,args);
基本的原理和实例3相同,不同点是最后一行,invoke的一个参数是null,因为这是静态方法,不需要借助实例运行。
5.新建实例
publicObjectnewInstance(StringclassName,Object[]args)throwsException{
ClassnewoneClass=Class.forName(className);
Constructorcons=newoneClass.getConstructor(argsClass);
returncons.newInstance(args);
这里说的方法是执行带参数的构造函数来新建实例的方法。
如果不需要参数,可以直接使用newoneClass.newInstance()来实现。
ClassnewoneClass=Class.forName(className):
第一步,得到要构造的实例的Class。
第6~第10行:
得到参数的Class数组。
Constructorcons=newoneClass.getConstructor(argsClass):
得到构造子。
cons.newInstance(args):
新建实例。
6.判断是否为某个类的实例
publicbooleanisInstance(Objectobj,Classcls){
returncls.isInstance(obj);
7.得到数组中的某个元素
publicObjectgetByArray(Objectarray,intindex){
returnArray.get(array,index);
4.性能问题和缺点
反射是一种强大的工具,但也存在一些不足。
一个主要的缺点是对性能有影响。
使用反射基本上是一种解释操作,您可以告诉JVM您希望做什么并且它满足您的要求。
这类操作总是慢于只直接执行相同的操作。
为了阐述使用反射的性能成本,我为本文准备了一组基准程序。
清单6是字段接入性能测试的一个摘用,包括基本的测试方法。
每种方法测试字段接入的一种形式--accessSame与同一对象的成员字段协作,accessOther使用可直接接入的另一对象的字段,accessReflection使用可通过反射接入的另一对象的字段。
在每种情况下,方法执行相同的计算--循环中简单的加/乘顺序。
清单6:
字段接入性能测试代码
publicintaccessSame(intloops){
m_value=0;
for(intindex=0;
index<
loops;
index++){
m_value=(m_value+ADDITIVE_VALUE)*
MULTIPLIER_VALUE;
returnm_value;
publicintaccessReference(intloops){
TimingClasstiming=newTimingClass();
timing.m_value=(timing.m_value+ADDITIVE_VALUE)*
returntiming.m_value;
publicintaccessReflection(intloops)throwsException{
try{
Fieldfield=TimingClass.class.
getDeclaredField("
m_value"
);
intvalue=(field.getInt(timing)+
ADDITIVE_VALUE)*MULTIPLIER_VALUE;
field.setInt(timing,value);
}catch(Exceptionex){
System.out.println("
Errorusingreflection"
throwex;
测试程序重复调用每种方法,使用一个大循环数,从而平均多次调用的时间衡量结果。
平均值中不包括每种方法第一次调用的时间,因此初始化时间不是结果中的一个因素。
在为本文进行的测试中,每次调用时我使用1000万的循环数,在1GHzPIIIm系统上运行。
三个不同LinuxJVM的计时结果如图1所示。
所有测试使用每个JVM的缺省设置。
图1:
字段接入时间
上表的对数尺度可以显示所有时间,但减少了差异看得见的影响。
在前两副图中(SunJVM),使用反射的执行时间超过使用直接接入的1000倍以上。
通过比较,IBMJVM可能稍好一些,但反射方法仍旧需要比其它方法长700倍以上的时间。
任何JVM上其它两种方法之间时间方面无任何显著差异,但IBMJVM几乎比SunJVM快一倍。
最有可能的是这种差异反映了SunHotSpotJVM的专业优化,它在简单基准方面表现得很糟糕。
反射有两个缺点。
第一个是性能问题。
当用于字段和方法接入时反射要远慢于直接代码。
性能问题的程度取决于程序中是如何使用反射的。
如果它作为程序运行中相对很少涉及的部分,缓慢的性能将不会是一个问题。
即使测试中最坏情况下的计时图显示的反射操作只耗用几微秒。
仅反射在性能关键的应用的核心逻辑中使用时性能问题才变得至关重要。
许多应用更严重的一个缺点是使用反射会模糊程序内部实际要发生的事情。
程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术会带来维护问题。
反射代码比相应的直接代码更复杂,正如性能比较的代码实例中看到的一样。
解决这些问题的最佳方案是保守地使用反射--仅在它可以真正增加灵活性的地方--记录其在目标类中的使用。