📄 java编程 的动态性,第 2部分:引入反射.htm
字号:
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>清单2中的代码获得构造函数并使用它来创建使用<CODE>String</CODE>s <CODE>"a"</CODE> 和
<CODE>"b"</CODE>的<CODE>TwoString</CODE> 类的一个实例:</P><A
name=IDA3GMQD><B>清单2:构造函数的反射调用</B></A><BR>
<TABLE bgColor=#cccccc border=1 cellPadding=5 cellSpacing=0
width="100%"><TBODY>
<TR>
<TD><PRE><CODE>
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);
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>清单2中的代码忽略了不同反射方法抛出的多种可能选中的例外类型。例外在 Javadoc API
描述中详细记录,因此为了简明起见,我将在所有程序实例中忽略它们。</P>
<P>尽管我在讨论构造函数主题,Java编程语言还定义了一种您可以用来使用<I>无参数</I>(或缺省)构造函数创建类的一个实例的特殊快捷方式。这种快捷方式嵌入到<CODE>Class</CODE>定义中,如下:</P>
<BLOCKQUOTE><CODE>Object newInstance()</CODE> -- 使用缺省函数创建新的实例</BLOCKQUOTE>
<P>即使这种方法只允许您使用一种特殊的构造函数,如果这正是您需要的,那么它将提供一种非常方便的快捷方式。当与JavaBeans协作时这项技术尤其有用,JavaBeans需要定义公共、无参数构造函数。</P>
<P><A name=IDAWHMQD><SPAN
class=atitle3>通过反射增加字段</SPAN></A>获得字段信息的<CODE>Class</CODE>
反射调用不同于那些用于接入构造函数的调用,在参数类型数组中使用了字段名:</P>
<UL>
<LI><CODE>Field getField(String name)</CODE> -- 获得命名的公共字段<BR><BR>
<LI><CODE>Field[] getFields()</CODE> -- 获得类的所有公共字段<BR><BR>
<LI><CODE>Field getDeclaredField(String name)</CODE> --
获得类声明的命名的字段<BR><BR>
<LI><CODE>Field[] getDeclaredFields()</CODE> -- 获得类声明的所有字段 </LI></UL>
<P>尽管与构造函数调用类似,在字段方面仍存在一个重要的区别:前两个变量返回可以通过类接入的公共字段的信息 --
即使它们来自于祖先类。后两个变量返回类直接声明的字段的信息 -- 与字段的接入类型无关。</P>
<P>调用返回的<CODE>java.lang.reflect.Field</CODE>实例定义所有主类型的<CODE>getXXX</CODE>
和 <CODE>setXXX</CODE> 方法,以及与对象引用协作的通用<CODE>get</CODE> 和 <CODE>set</CODE>
方法。您可以根据实际的字段类型自行选择一种适当的方法,而<CODE>getXXX</CODE>
方法将自动处理扩展转换(如使用<CODE>getInt</CODE> 方法来检索一个字节值)。</P>
<P>清单3显示使用字段反射方法的一个实例,以方法的格式根据名称增加对象的<CODE>int</CODE>字段 :</P><B>清单</B><A
name=IDAKSMQD><B>3:通过反射增加一个字段</B></A><BR>
<TABLE bgColor=#cccccc border=1 cellPadding=5 cellSpacing=0
width="100%"><TBODY>
<TR>
<TD><PRE><CODE>
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;
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>这种方法开始展示了反射带来的某些灵活性。与特定的类协作不同,<CODE>incrementField</CODE> 使用传
入的对象的<CODE>getClass</CODE> 方法来查找类信息,然后直接在该类中查找命名的字段。</P>
<P><A name=IDA4SMQD><SPAN
class=atitle3>通过反射增加方法</SPAN></A><BR>获得方法信息的<CODE>Class</CODE>
反射调用与用于构造函数和字段的调用非常类似:</P>
<UL>
<LI><CODE>Method getMethod(String name, Class[] params)</CODE> --
使用特定的参数类型,获得命名的公共方法<BR><BR>
<LI><CODE>Method[] getMethods()</CODE> -- 获得类的所有公共方法<BR><BR>
<LI><CODE>Method getDeclaredMethod(String name, Class[] params)</CODE>
-- 使用特写的参数类型,获得类声明的命名的方法<BR><BR>
<LI><CODE>Method[] getDeclaredMethods()</CODE> -- 获得类声明的所有方法 </LI></UL>
<P>与字段调用一样,前两个变量返回可以通过类接入的公共方法的信息 --
即使它们来自于祖先类。后两个变量返回类声明的方法的信息,与方法的接入类型无关。</P>
<P>调用返回的<CODE>java.lang.reflect.Method</CODE>实例定义一种<CODE>invoke</CODE>方法,您可以用来在正在定义的类的一个实例上调用方法。这种<CODE>invoke</CODE>
方法使用两个参数,为调用提供类实例和参数值数组。</P>
<P>清单4进一步阐述字段实例,显示反射正在运行的方法的一个实例。这种方法增加一个定义有<CODE>get</CODE> 和
<CODE>set</CODE>方法的<CODE>int</CODE>
JavaBean属性。例如,如果对象为一个整数<CODE>count</CODE>值定义了<CODE>getCount</CODE> 和
<CODE>setCount</CODE>
方法,您可以在一次调用中向该方法传递“count”作为<CODE>name</CODE>参数,以增加该值。</P><A
name=IDATVMQD><B>清单4:通过反射增加一个JavaBean 属性</B></A><BR>
<TABLE bgColor=#cccccc border=1 cellPadding=5 cellSpacing=0
width="100%"><TBODY>
<TR>
<TD><PRE><CODE>
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;
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>为了遵循JavaBeans惯例,我把属性名的首字母改为大写,然后预先考虑 <CODE>get</CODE>
来创建读方法名,<CODE>set</CODE>来创建写方法名。JavaBeans读方法仅返回值,而写方法使用值作为唯一的参数,因此我规定方法的参数类型以进行匹配。最后,该惯例要求方法为公共,因此我使用查找格式,查找类上可调用的公共方法。</P>
<P>这一实例是第一个我使用反射传递主值的实例,因此现在我们来看看它是如何工作的。基本原理很简单:无论什么时候您需要传递主值,只需用相应封装类的一个实例(在<CODE>java.lang</CODE>包中定义)来替换该类主值。这可以应用于调用和返回。因此,当我在实例中调用<CODE>get</CODE>方法时,我预计结果为实际<CODE>int</CODE>属性值的<CODE>java.lang.Integer</CODE>封装。</P>
<P><A name=IDAZWMQD><SPAN
class=atitle3>反射数组</SPAN></A><BR>数组是Java编程语言中的对象。与所有对象一样,它们都有类。如果您有一个数组,使用标准<CODE>getClass</CODE>方法,您可以获得该数组的类,就象任何其它对象一样。但是,<I>不通过</I>现有的实例来获得类不同于其它类型的对象。即使您有一个数组类,您也不能直接对它进行太多的操作
-- 反射为标准类提供的构造函数接入不能用于数组,而且数组没有任何可接入的字段,只有基本的<CODE>java.lang.Object</CODE>
方法定义用于数组对象。</P>
<P>数组的特殊处理使用<CODE>java.lang.reflect.Array</CODE>类提供的静态方法的集合。该类中的方法使您能够创建新数组,获得数组对象的长度,读和写数组对象的索引值。</P>
<P>清单5显示了一种重新调整现有数组大小的有效方法。它使用反射来创建相同类型的新数组,然后在返回新数组之前,在老数组中复制所有数据。</P><A
name=IDATXMQD><B>清单 5:通过反射来扩展一个数组</B></A><BR>
<TABLE bgColor=#cccccc border=1 cellPadding=5 cellSpacing=0
width="100%"><TBODY>
<TR>
<TD><PRE><CODE>
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;
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P><A name=IDA3XMQD><SPAN
class=atitle2>安全性和反射</SPAN></A><BR>在处理反射时安全性是一个较复杂的问题。反射经常由框架型代码使用,由于这一点,您可能希望框架能够全面接入您的代码,无需考虑常规的接入限制。但是,在其它情况下,不受控制的接入会带来严重的安全性风险,如当代码在不值得信任的代码共享的环境中运行时。</P>
<P>由于这些互相矛盾的需求,Java编程语言定义一种多级别方法来处理反射的安全性。基本模式是对反射实施与应用于源代码接入相同的的限制:</P>
<UL>
<LI>从任意位置到类公共组件的接入
<LI>类自身外部无任何到私有组件的接入
<LI>受保护和打包(缺省接入)组件的有限接入 </LI></UL>
<P>不过-至少某些时候,围绕这些限制有一种简单的方法。我在前面实例中使用的<CODE>Constructor</CODE>、<CODE>Field</CODE>
和 <CODE>Method</CODE> 类都扩展了一个普通的基本类--
<CODE>java.lang.reflect.AccessibleObject</CODE>
类。该类定义一种<CODE>setAccessible</CODE>方法,使您能够启动或关闭对这些类中其中一个类的实例的接入检测。唯一的问题在于如果使用了安全性管理器,它将检测正在关闭接入检测的代码是否许可了这样做。如果未许可,安全性管理器抛出一个例外。</P>
<P>清单6展示了一个程序,在<A
href="http://www-900.ibm.com/developerWorks/cn/java/j-dyn0603/#code1">清单
1</A><CODE>TwoString</CODE> 类的一个实例上使用反射来显示安全性正在运行:</P><A
name=IDAMZMQD><B>清单 6:反射安全性正在运行</B></A><BR>
<TABLE bgColor=#cccccc border=1 cellPadding=5 cellSpacing=0
width="100%"><TBODY>
<TR>
<TD><PRE><CODE>
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);
}
}
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>如果您编译了这一程序,不使用任何特定参数直接从命令行运行,它将在<CODE>field.get(inst)</CODE>调用中抛出一个<CODE>IllegalAccessException</CODE>。如果您未注释<CODE>field.setAccessible(true)</CODE>代码行,那么重新编译并重新运行该代码,它将取得成功。最后,如果您在命令行添加了JVM参数<CODE>-Djava.security.manager</CODE>以实现安全性管理器,它将再次失败,除非您定义了<CODE>ReflectSecurity</CODE>类的许可权限。</P>
<P><A name=IDAM0MQD><SPAN
class=atitle2>反射性能</SPAN></A><BR>反射是一种强大的工具,但也存在一些不足。一个主要的缺点是对性能有影响。使用反射基本上是一种解释操作,您可以告诉JVM您希望做什么并且它满足您的要求。这类操作总是慢于只直接执行相同的操作。为了阐述使用反射的性能成本,我为本文准备了一组基准程序(见<A
href="http://www-900.ibm.com/developerWorks/cn/java/j-dyn0603/#resources">参考资料</A>,完整代码链接)。</P>
<P>清单7是字段接入性能测试的一个摘用,包括基本的测试方法。每种方法测试字段接入的一种形式 -- <CODE>accessSame</CODE>
与同一对象的成员字段协作,<CODE>accessOther</CODE>
使用可直接接入的另一对象的字段,<CODE>accessReflection</CODE>
使用可通过反射接入的另一对象的字段。在每种情况下,方法执行相同的计算 -- 循环中简单的加/乘顺序。</P><A
name=IDAH1MQD><B>清单 7:字段接入性能测试代码</B></A><BR>
<TABLE bgColor=#cccccc border=1 cellPadding=5 cellSpacing=0
width="100%"><TBODY>
<TR>
<TD><PRE><CODE>
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);
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -