??xml version="1.0" encoding="utf-8" standalone="yes"?>
Java动态程序设计:(x)反射介绍
muzi_li 译 版本Q?.0
Java动态程序设计:(x)反射介绍
使用q行的类的信息你的E序设计更加灉|
反射授予了你的代码访问装载进JVM内的Javacȝ内部信息的权限,q且允许你编写在E序执行期间与所选择的类的一同工作的代码Q而不是在源代码中。这U机制得反成为创建灵zȝ应用E序的强大工P但是要小心的是,如果使用不恰当,反射?x)带来很大的副作用。在q篇文章中,软g咨询NDennis Sosnoski 介绍了反的使用Q同时还介绍了一些用反所要付出的代h(hun)。在q里Q你可以扑ֈJava反射API是如何在q行时让你钩入对象的?BR>
在第一部分Q我向你介绍了JavaE序设计的类以及cȝ装蝲。那文章中描述了很多出现在Java二进制类格式中的信息Q现在我来介l在q行时用反API讉K和用这些信息的基础。ؓ(f)了那些已经了解反射基础的开发h员对q些事情感兴,我还?x)介l一些反与直接讉K的在性能斚w的比较?BR>
使用反射与和metadata(描述其它数据的数?一些工作的JavaE序设计是不同的。通过Java语言反射来访问的元数据的Ҏ(gu)cd是在JVM内部的类和对象的描述。反你可以在q行时访问各U类信息Q它甚至可以你让在运行时d属性字Dc调用所选择的类的方法?BR>
反射是一个强大的工具Q它让你建立灉|能够在运行时l装的代码,而不需要连接组仉的源代码。反的一些特征也带来一些问题。在q章中,我将?x)探I在应用E序中不打算使用反射的原因,以ؓ(f)什么用它的原因。在你了解到q些利弊之后Q你׃(x)在好处大于缺点的时候做出决定?BR>
初识class
使用反射的vҎ(gu)L一个java.lang.Classcȝ实例。如果你与一个预先确定的cM同工作,Java语言为直接获得Classcȝ实例提供了一个简单的快捷方式。例如:(x)
Class clas = MyClass.class;
当你使用q项技术的时候,所有与装蝲cL关的工作都发生在q后。如果你需要在q行时从外部的资源中dcdQ用上面这U方法是不会(x)辑ֈ目的的,相反你需要用类装蝲器来查找cȝ信息Q方法如下所C:(x)
// "name" is the class name to load
Class clas = null;
try {
clas = Class.forName(name);
} catch (ClassNotFoundException ex) {
// handle exception case
}
// use the loaded class
如果cdl装载,你将?x)找到当前在在的cȝ信息。如果类q没有被装蝲Q那么类装蝲器将?x)装载它Qƈ且返回最q创建的cȝ实例?BR>
关于cȝ反射
Class对象l予你了所有的用于反射讉Kcȝ元数据的基本钩子。这些元数据包括有关cȝ自n信息Q例如象cȝ包和子类Q还有这个类所实现的接口,q包括这个类所定义的构造器、属性字D以及方法的详细信息。后面的q些Ҏ(gu)我们在程序设计过U经怋用的Q因此在q一节的后面我会(x)l出一些用q些信息来工作的例子?BR>
对于cȝ构造中的每一U类型(构造器、属性字Dc方法)Qjava.lang.Class提供了四U独立的反射调用以不的方式来讉Kcȝ信息。下面列Zq四U调用的标准形式Q它是一l用于查找构造器的调用?BR>
Constructor getConstructor(Class[] params) 使用指定的参数类型来获得公共的构造器Q?BR>Constructor[] getConstructors() 获得q个cȝ所有构造器Q?BR>Constructor getDeclaredConstructor(Class[] params) 使用指定的参数类型来获得构造器Q忽略访问的U别Q?BR>Constructor[] getDeclaredConstructors() 获得q个cȝ所有的构造器Q忽略访问的U别Q?BR>
上述的每一U方法都q回一或多个java.lang.reflect.Constructor的实例。Constructorcd义了一个需要一个对象数据做为唯一参数的newInstanceҎ(gu)Q然后返回一个最q创建的原始cȝ实例。对象数l是在构造器调用时所使用的参数倹{例如,假设你有一个带有一对String cd做ؓ(f)参数的构造器的TwoStringc,代码如下所C:(x)
public class TwoString {
private String m_s1, m_s2;
public TwoString(String s1, String s2) {
m_s1 = s1;
m_s2 = s2;
}
}
下面的代码显C如何获得TwoStringcȝ构造器Qƈ使用字符东ya”和“b”来创徏一个实例:(x)
Class[] types = new Class[] { String.class, String.class };
Constructor cons = TwoString.class.getConstructor(types);
Object[] args = new Object[] { "a", "b" };
TwoString ts = cons.newInstance(args);
上面的代码忽略了几种可能的被不同的反方法抛出的异常查的cd。这些异常在Javadoc API中有详细的描qͼ因此为简便v见,我会(x)在所有的代码中忽略它们?BR>
在我涉及到构造器q个主题ӞJava语言也定义了一个特D的没有参数的(或默认)构造器快捷Ҏ(gu)Q你能用它来创Z个类的实例。这个快h法象下面的代码这栯嵌入到类的自定义中:(x)
Object newInstance() ?使用默认的构造器创徏新的实例?BR>
管q种Ҏ(gu)只让你用一个特D的构造器Q但是如果你需要的话,它是非常便利的快h式。这Ҏ(gu)术在使用JavaBeans工作的时候尤其有用,因ؓ(f)JavaBeans需要定义一个公q、没有参数的构造器?BR>
通过反射来查扑ֱ性字D?BR>
Classcd调用访问属性字D信息与那些用于讉K构造器的方法类|在有数组cd的参数的使用属性字D名来替代:(x)使用Ҏ(gu)如下所C:(x)
Field getField(String name) --获得由name指定的具有publicU别的属性字D?BR>Field getFields() ?获得一个类的所有具有publicU别的属性字D?BR>Field getDeclaredField(String name) ?获得由name指定的被cd明的属性字D?BR>Field getDeclaredFields() ?获得q定义的所有的属性字D?BR>
管与构造器的调用很怼Q但是在提到属性字D늚时候,有一个重要的差别Q前两个Ҏ(gu)q回能过cL讉K的公共(publicQ属性字D늚信息Q包括那些来自于类的属性字D)Q后两个Ҏ(gu)q回q直接声明的所有的属性字D(忽略了属性字D늚讉KcdQ?BR>
Java.lang.reflect.Field的实例通过调用定义好的getXXX和setXXXҎ(gu)来返回所有的原始的数据类型,像普通的与对象引用一起工作的get和setҎ(gu)一栗尽getXXXҎ(gu)?x)自动地处理数据cd转换Q例如用getIntҎ(gu)来获取一个bytecd的|Q但使用一个适当Z实际的属性字D늱型的Ҏ(gu)是应该优先考虑的?BR>
下面的代码显CZ如何使用属性字D늚反射Ҏ(gu)Q通过指定属性字D名Q找C个对象的intcd的属性字D,q给q个属性字D值加1?BR>public int incrementField(String name, Object obj) throws... {
Field field = obj.getClass().getDeclaredField(name);
int value = field.getInt(obj) + 1;
field.setInt(obj, value);
return value;
}
q个Ҏ(gu)开始展C些用反所可能带来的灵zL,它优于与一个特定的cM同工作,incrementFieldҎ(gu)把要查找的类信息的对象传递给getClassҎ(gu)Q然后直接在那个cM查找命名的属性字Dc?BR>
通过反射来查找方?BR>Class反射调用讉KҎ(gu)的信息与讉K构造器和字D属性的Ҏ(gu)非常怼Q?BR> Method getMethod(String name,Class[] params) --使用指定的参数类型获得由name参数指定的publiccd的方法?BR>Mehtod[] getMethods()?获得一个类的所有的publiccd的方?BR>Mehtod getDeclaredMethod(String name, Class[] params)?使用指定的参数类型获得由name参数所指定的由q个cd明的Ҏ(gu)?BR>Method[] getDeclaredMethods() ?获得q个cL声明的所有的Ҏ(gu)
与属性字D늚调用一P前两个方法返回通过q个cȝ实例可以讉K的publiccd的方?包括那些l承于超cȝҎ(gu)。后两个Ҏ(gu)q回p个类直接声明的方法的信息Q而不方法的讉Kcd?BR>
通过调用q回的Java.lang.reflect.Mehtod实例定义了一个invokeҎ(gu)Q你可以使用它来调用定义cȝ有关实例。这个invokeҎ(gu)需要两个参敎ͼ一个是提供q个Ҏ(gu)的类的实例,一个是调用q个Ҏ(gu)所需要的参数值的数组?BR>
下面l出了比属性字D늚例子更加深入的例子,它显CZ一个的Ҏ(gu)反射的例子,q个Ҏ(gu)使用get和setҎ(gu)来给JavaBean定义的intcd的属性做增量操作。例如,如果对象Z个整数类型count属性定义了getCount和setCountҎ(gu)Q那么ؓ(f)了给q个属性做增量q算Q你可以把“count”做为参数名传递给调用的这个方法中。示例代码如下:(x)
public int incrementProperty(String name, Object obj) {
String prop = Character.toUpperCase(name.charAt(0)) +
name.substring(1);
String mname = "get" + prop;
Class[] types = new Class[] {};
Method method = obj.getClass().getMethod(mname, types);
Object result = method.invoke(obj, new Object[0]);
int value = ((Integer)result).intValue() + 1;
mname = "set" + prop;
types = new Class[] { int.class };
method = obj.getClass().getMethod(mname, types);
method.invoke(obj, new Object[] { new Integer(value) });
return value;
}
Ҏ(gu)JavaBeans的规范,我把属性名的第一个字母{换ؓ(f)大写Q然后在前面加上“get”来建立d属性值的Ҏ(gu)名,在属性名前加上“set”来建立讄属性值的Ҏ(gu)名。JavaBeans的读Ҏ(gu)只返回属性|写方法只需要要写入的值做为参敎ͼ因此我指定了与这个方法相匚w的参数类型。最后规范规定这两个Ҏ(gu)应该是publiccd的,因此我用了查找相关cȝpubliccdҎ(gu)的调用Ş式?BR>
q个例子我首先用反传递一个原始类型的|因此让我们来看一下它是怎样工作的。基本的原理是简单的Q无Z么时候,你需要传递一个原始类型的|你只要替换相应的装原始cd的(在java.lang 包中定义的)的类的实例就可以了。这U方法可应用于调用和q回。因此在我的例子中调用getҎ(gu)Ӟ我预期的l果是一个由java.lang.IntegercL装的实际的intcd的属性倹{?BR>
反射数组
在Java语言中数l是对象Q象其它所有的对象一P它有一些类。如果你有一个数l,你可以和其它M对象一样用标准的getClassҎ(gu)来获得这个数l的c,但是你获得的q个cM其它的对象类型相比,不同之处在它没有一个现存的工作实例。即使你有了一个数l类之后Q你也不能够直接用它来做M事情Q因为通过反射为普通的cL提供的构造器讉K不能为数l工作,q且数组没有M可访问的属性字D,只有基本的ؓ(f)数组对象定义的java.lang.Objectcd的方法?BR>
数组Ҏ(gu)处理要用java.lang.reflect.ArraycL供的一个静态方法的集合Q这个类中的Ҏ(gu)可以让你创徏新的数组Q获得一个数l对象的长度Q以及读写一个数l对象的索引倹{?BR>
下面的代码显CZ有效调整一个现存数l的寸的方法。它使用反射来创Z个相同类型的新数l,然后在返回这个新数组之前把原数组中的所有的数据复制到新的数l中?BR>public Object growArray(Object array, int size) {
Class type = array.getClass().getComponentType();
Object grown = Array.newInstance(type, size);
System.arraycopy(array, 0, grown, 0,
Math.min(Array.getLength(array), size));
return grown;
}
安全与反?BR>
在处理反的时候,安全是一个复杂的问题。反正常被框架cd的代码用,q因P你可能会(x)l常要求框架不关心普通的讉K限制来完全访问你的代码。然而,自由的访问可能会(x)在其它的一些实例中产生一些风险,例如在代码在一个不被信ȝ代码׃n环境中被执行的时候?BR>
因ؓ(f)q些冲突的需要,Java语言定义了一个多U方法来处理反射安全。基本的模式是在反射h源码讉K的时候强制用如下相同的U束限制Q?BR>讉Kq个cM来自M地方的publiclgQ?BR>不访问这个类本n外部的privatelgQ?BR>限制讉Kprotected和package(默认讉K)lg?BR>
围绕q些限制有一个简单的Ҏ(gu)Q我在前面的例子中所使用的所有构造器、属性字Dc以及类的方法都扩展于一个共同的基类???java.lang.reflect.AccessibleObjectcR这个类定义了一个setAccessibleҎ(gu)Q这个方法可以让你打开或关闭这些对cȝ实例的访问检查。如果安全管理器被设|ؓ(f)关闭讉K查,那么允怽讉KQ否则不允许Q安全管理器?x)抛Z个异常?BR>
下面是一个用反向来演示q种行ؓ(f)的TwoStringcȝ实例?BR>public class ReflectSecurity {
public static void main(String[] args) {
try {
TwoString ts = new TwoString("a", "b");
Field field = clas.getDeclaredField("m_s1");
// field.setAccessible(true);
System.out.println("Retrieved value is " +
field.get(inst));
} catch (Exception ex) {
ex.printStackTrace(System.out);
}
}
}
如果你编译这D代码ƈ且直接用不带Q何参数的命o行命令来q行q个E序Q它?x)抛Z个关于field.get(inst)调用的IllegalAccessException异常Q如果你L上面代码中field.setAccessible(true)行的注释Q然后编译ƈ重新q行代码Q它?yu)׃?x)成功执行。最后,如果你在命o行给JVMd一个Djava.security.manager参数Q得安全管理器可用Q那么它又会(x)p|Q除非你为ReflectSecuritycd义安全许可?BR>
反射性能
反射是一个强大的工具Q但是也?x)带一些缺炏V主要缺点之一是Ҏ(gu)能的媄响。用反是基本的解释性操作,你告诉JVM你要做什么,它就?x)?f)你做什么。这U操作类型L比直接做同样的操作要慢。ؓ(f)了演CZ用反所要付出的性能代h(hun)Q我文章准备了一套基准程序(可以从资源中下蝲Q?BR>
下面列出一D|自于属性字D늚讉K性能试的摘要,它包括基本的试Ҏ(gu)。每个方法测试一U访问属性字D늚形式QaccessSameҎ(gu)和本对象的成员字D一起工作,accessReferenceҎ(gu)直接使用另外的对象属性字D|存取QaccessReflection通过反射使用另一个对象的属性字D|存取Q每个方法都使用相同的计???在@环中单的?乘运?BR>public int accessSame(int loops) {
m_value = 0;
for (int index = 0; index < loops; index++) {
m_value = (m_value + ADDITIVE_VALUE) *
MULTIPLIER_VALUE;
}
return m_value;
}
public int accessReference(int loops) {
TimingClass timing = new TimingClass();
for (int index = 0; index < loops; index++) {
timing.m_value = (timing.m_value + ADDITIVE_VALUE) *
MULTIPLIER_VALUE;
}
return timing.m_value;
}
public int accessReflection(int loops) throws Exception {
TimingClass timing = new TimingClass();
try {
Field field = TimingClass.class.
getDeclaredField("m_value");
for (int index = 0; index < loops; index++) {
int value = (field.getInt(timing) +
ADDITIVE_VALUE) * MULTIPLIER_VALUE;
field.setInt(timing, value);
}
return timing.m_value;
} catch (Exception ex) {
System.out.println("Error using reflection");
throw ex;
}
}
试E序在一个大循环中反复的调用每个Ҏ(gu)Q在调用l束后计^均时间。每个方法的W一ơ调用不包括在^均gQ因些初始化旉不是影响l果的因素。ؓ(f)q篇文章所做的试q行Q我为每个调用用了10000000的@环计敎ͼ代码q行?GHz PIIIpȝ上。ƈ且分别用了三个不同的Linux JVMQ对于每个JVM都用了默认讄Q测试结果如下图所C:(x)
上面的图表的d可以昄整个试范围Q但是那L(fng)话就?x)减差别的昄效果。这个图表中的前两个是用SUN的JVM的进行测试的l果图,使用反射的执行时间比使用直接讉K的时间要过1000多倍。最后一个图是用IBM的JVM所做的试Q通过比较要SUN的JVM执行效率要高一些,但是使用反射的方法依然要比其它方法超?00多倍。虽然IBM的JVM要比SUN的JVM几乎要快两倍,但是在用反之外的两种Ҏ(gu)之间Q对于Q何的JVM在执行效率上没有太大的差别。最大的可能是,q种差别反映了通过Sun Hot Spot JVMs在简化基准方面所做的专门优化很少?BR>
除了属性字D访问时间的试以外Q我Ҏ(gu)法做了同L(fng)试。对于方法的调用Q我偿试了与属性字D访问测试一L(fng)三种方式Q用额外使用了没有参数的Ҏ(gu)的变量与传递ƈq回一个值的Ҏ(gu)调用相对比。下面的代码昄了用传递ƈq回值的调用方式q行试的三U方法?BR>public int callDirectArgs(int loops) {
int value = 0;
for (int index = 0; index < loops; index++) {
value = step(value);
}
return value;
}
public int callReferenceArgs(int loops) {
TimingClass timing = new TimingClass();
int value = 0;
for (int index = 0; index < loops; index++) {
value = timing.step(value);
}
return value;
}
public int callReflectArgs(int loops) throws Exception {
TimingClass timing = new TimingClass();
try {
Method method = TimingClass.class.getMethod
("step", new Class [] { int.class });
Object[] args = new Object[1];
Object value = new Integer(0);
for (int index = 0; index < loops; index++) {
args[0] = value;
value = method.invoke(timing, args);
}
return ((Integer)value).intValue();
} catch (Exception ex) {
System.out.println("Error using reflection");
throw ex;
}
}
下图昄我用这些方法的试l果Q这里再一ơ显CZ反射要比其它的直接访问要慢很多。虽然对于无参数的案例,执行效率从SUN1.3.1JVM的慢几百倍到IBM的JVM慢不?0倍,与属性字D访问案例相比,差别不是很大Q这U情늚部分原因是因为java.lang.Integer的包装器需要传递和q回intcd的倹{因为Intergers是不变的Q因此就需要ؓ(f)每个Ҏ(gu)的返回生成一个新|q就增加了相当大的系l开销?BR>
反射的性能是SUN在开?.4JVM旉点关注的一个领域,从上囑֏以看到改善的l果。Sun1.4.1JVM对于q种cd的操作比1.3.1版有了很大的提高Q要我的试中要快大U?倍。IBM?.4.0JVM对于q种试提供了更好的性能Q它的运行效率要比Sun1.4.1JVM快两C倍?BR>
我还Z用反创建对象编写了一个类似的效率试E序。虽然这个例子与属性字D和Ҏ(gu)调用相比差别不是很大Q但是在Sun1.3.1JVM上调用newInstanceQ)Ҏ(gu)创徏一个简单的java.lang.Object大约比直接用new Object()Ҏ(gu)?2倍的旉Q在IBM1.4.0JVM上大U要?倍的旉Q在Sun1.4.1JVM上大U要?倍的旉。对于Q何用于测试的JVMQ用Array.newInstance(Type,size)Ҏ(gu)创徏一个数l所需要的旉比用new tye[size]所p的时间大U要长两倍,随着数组民尺寸的增长Q这两种Ҏ(gu)的差别的随之减?BR>
反射概要ȝ
Java 语言的反机制提供了一U非帔R用的动态连接程序组件的Ҏ(gu)。它允许你的E序创徏和维护Q何类的对象(服从安全限制Q,而不需要提前对目标c进行硬~码。这些特征得反在创徏与对象一同工作的cd中的通用Ҏ(gu)斚w非常有用。例如,反射l常被用于那些数据库QXML、或者其它的外部的持久化对象的框架中?BR>
反射q有两个~点Q一个是性能问题。在使用属性字D和Ҏ(gu)讉K的时候,反射要比直接的代码访问要慢很多。至于对影响的程度,依赖于在E序中怎样使用反射。如果它被用作一个相关的很少发生的程序操作中Q那么就不必兛_降低的性能Q即使在我的试中所展示的最耗时的反操作的囑Ş中也只是几微U的旉。如果要在执行应用程序的核心逻辑中用反,性能问题才成Z个要严肃对象的问题?BR>
对于很多应用中的存在的缺Ҏ(gu)使用反射可以使你的实际的代码内部逻辑变得模糊不清。程序员都希望在源代码中看到一个程序的逻辑以及象绕q源代码的反所可能产生的维护问题这L(fng)一些技术。反代码也比相应的直接代码要复杂一些,像在性能比较的代码实例看到那栗处理这些问题的最好方法是可能少使用反射Q只有在一些增加灵zL的地方来用它?BR>
在下一文章中Q我给Z个更加详l的如何使用反射的例子。这个例子提供了一个用于处理传递给一个Java应用E序的命令行参数的API。在避免q的同Ӟ它也昄了反的强大的功能,反射能够使用的你的命令处理变得的单吗Q你可以在Java 动态程序设计的W三部分中找到答案?BR>
]]>
//program: W卡?dng)数学公?BR>//files: Heart.java
import java.applet.*;
import java.awt.*;
public class Heart extends Applet
{
int AppletWidth,AppletHeight;
Image OffScreen;
Graphics drawOffScreen;
public void init()
{
setBackground(Color.black);
//取得昄区域
AppletWidth = getSize().width;
AppletHeight = getSize().height;
//建立ơ画?BR> OffScreen = createImage(AppletWidth,AppletHeight);
drawOffScreen = OffScreen.getGraphics();
}
public void paint(Graphics g)
{
drawOffScreen.clearRect(0,0,AppletWidth,AppletHeight);
drawOffScreen.setColor(Color.white);
int i,j;
double x,y,r;
for(i = 0;i <= 90;i++)
for(j = 0;j <= 90;j++)
{
//转换为直角坐?BR> r = Math.PI/45*i*(1-Math.sin(Math.PI/45*j))*18;
x = r*Math.cos(Math.PI/45*j)*Math.sin(Math.PI/45*i)+AppletWidth/2;
y = -r*Math.sin(Math.PI/45*j)+AppletHeight/4;
//投媄到xzq面
drawOffScreen.fillOval((int)x,(int)y,2,2);
}
g.drawImage(OffScreen,0,0,this);
}
}