反射使您的程序代码能够接入装载到JVM中的类的内部信息,允许您编写与执行时,而不是源代码中选定的类协作的代码。这使反射成 为构建灵活的应用的主要工具。但需注意的是 --如果使用不当,反射的成本很高。在Java平台系列的第2部分中,软件顾问Dennis Sosnoski介绍了如何使用反射,以及某些相关的成本。您还将找到JavaReflection API如何使您能够在运行时关联对象。
在“ Java编程的动态性,第1部分,”我为您介绍了Java编程类和类装入。该篇文章介绍了一些Java二进制类格式的相关信息。这个月我将阐述使用Java反射API来在运行时接入和使用一些相同信息的基础。为了使已经熟知反射基础的开发人员关注本文,我将在文章中包括反射性能如何与直接接入相比较。
使用反射不同于常规的Java编程,其中它与 元数据--描述其它数据的数据协作。Java语言反射接入的特殊类型的原数据是JVM中类和对象的描述。反射使您能够运行时接入广泛的类信息。它甚至使您能够读写字段,调用运行时选择的类的方法。
反射是一种强大的工具。它使您能够创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代表链接。但反射的某些方面存在一些疑问。在本文中,我将深入讨论为什么您可能 不希望在程序中使用反射,以及您应该这样做的理由。在了解了权衡性分析之后,您可以自行决定是否利大于弊。
初学者的类
使用反射的启点总是 java.lang.Class
实例。如果您希望与预先定义的类协作,那么Java语言提供一种直接获得 Class
实例的简便快捷方式:
Class clas = MyClass.class;
当您使用这一项技术时,装入类涉及的所有工作在幕后进行。但是,如果您需要在运行时从某些外部源读取类名,这种方法并不适合。实际上,您需要使用一个类装入器来查找类信息。以下介绍一种方法:
// "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
|
如果已经装入了类,您将得到现有的 Class
信息。如果类未被装入,类装入器将现在装入并返回新创建的类实例。
基于类的反射
Class
对象为您提供接入类元数据的反射的所有基本hook。这类元数据包括关于类自身的信息,如包和类的父类,以及该类实施的接口。它还包括该类定义的构造函数、字段和方法的详细信息。这些最后的项目都是编程中最经常使用的项目, 因此我将在本小节的稍后部分给出一些与它们协作的实例。
对于以下三类组件中的任何一类来说 -- 构造函数、字段和方法 -- java.lang.Class
提供四种独立的反射调用,以不同的方式来获得信息。调用都遵循一种标准格式。以下是用于查找构造函数的一组反射调用:
Constructor getConstructor(Class[] params)
-- 获得使用特殊的参数类型的公共构造函数,
Constructor[] getConstructors()
-- 获得类的所有公共构造函数
Constructor getDeclaredConstructor(Class[] params)
-- 获得使用特定参数类型的构造函数(与接入级别无关)
Constructor[] getDeclaredConstructors()
-- 获得类的所有构造函数(与接入级别无关)
每类这些调用都返回一个或多个 java.lang.reflect.Constructor
函数。这种 Constructor
类定义 newInstance
方法,它采用一组对象作为其唯一的参数,然后返回新创建的原始类实例。该组对象是用于构造函数调用的参数值。作为解释这一工作流程的实例,假设您有一个 TwoString
类和一个使用一对 String
s的构造函数,如清单1所示:
清单1:从一对字符串创建的类 
public class TwoString {
private String m_s1, m_s2;
public TwoString(String s1, String s2) {
m_s1 = s1;
m_s2 = s2;
}
}
|
清单2中的代码获得构造函数并使用它来创建使用 String
s "a"
和 "b"
的 TwoString
类的一个实例:
清单2:构造函数的反射调用
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);
|
清单2中的代码忽略了不同反射方法抛出的多种可能选中的例外类型。例外在 Javadoc API 描述中详细记录,因此为了简明起见,我将在所有程序实例中忽略它们。
尽管我在讨论构造函数主题,Java编程语言还定义了一种您可以用来使用 无参数(或缺省)构造函数创建类的一个实例的特殊快捷方式。这种快捷方式嵌入到 Class
定义中,如下:
Object newInstance()
-- 使用缺省函数创建新的实例
即使这种方法只允许您使用一种特殊的构造函数,如果这正是您需要的,那么它将提供一种非常方便的快捷方式。当与JavaBeans协作时这项技术尤其有用,JavaBeans需要定义公共、无参数构造函数。
通过反射增加字段
获得字段信息的 Class
反射调用不同于那些用于接入构造函数的调用,在参数类型数组中使用了字段名:
Field getField(String name)
-- 获得命名的公共字段
Field[] getFields()
-- 获得类的所有公共字段
Field getDeclaredField(String name)
-- 获得类声明的命名的字段
Field[] getDeclaredFields()
-- 获得类声明的所有字段
尽管与构造函数调用类似,在字段方面仍存在一个重要的区别:前两个变量返回可以通过类接入的公共字段的信息 -- 即使它们来自于祖先类。后两个变量返回类直接声明的字段的信息 -- 与字段的接入类型无关。
调用返回的 java.lang.reflect.Field
实例定义所有主类型的 getXXX
和 setXXX
方法,以及与对象引用协作的通用 get
和 set
方法。您可以根据实际的字段类型自行选择一种适当的方法,而 getXXX
方法将自动处理扩展转换(如使用 getInt
方法来检索一个字节值)。
清单3显示使用字段反射方法的一个实例,以方法的格式根据名称增加对象的 int
字段 :
清单3:通过反射增加一个字段
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;
}
|
这种方法开始展示了反射带来的某些灵活性。与特定的类协作不同, incrementField
使用传 入的对象的 getClass
方法来查找类信息,然后直接在该类中查找命名的字段。
通过反射增加方法
获得方法信息的 Class
反射调用与用于构造函数和字段的调用非常类似:
Method getMethod(String name, Class[] params)
-- 使用特定的参数类型,获得命名的公共方法
Method[] getMethods()
-- 获得类的所有公共方法
Method getDeclaredMethod(String name, Class[] params)
-- 使用特写的参数类型,获得类声明的命名的方法
Method[] getDeclaredMethods()
-- 获得类声明的所有方法
与字段调用一样,前两个变量返回可以通过类接入的公共方法的信息 -- 即使它们来自于祖先类。后两个变量返回类声明的方法的信息,与方法的接入类型无关。
调用返回的 java.lang.reflect.Method
实例定义一种 invoke
方法,您可以用来在正在定义的类的一个实例上调用方法。这种 invoke
方法使用两个参数,为调用提供类实例和参数值数组。
清单4进一步阐述字段实例,显示反射正在运行的方法的一个实例。这种方法增加一个定义有 get
和 set
方法的 int
JavaBean属性。例如,如果对象为一个整数 count
值定义了 getCount
和 setCount
方法,您可以在一次调用中向该方法传递“count”作为 name
参数,以增加该值。
清单4:通过反射增加一个JavaBean 属性
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;
}
|
为了遵循JavaBeans惯例,我把属性名的首字母改为大写,然后预先考虑 get
来创建读方法名, set
来创建写方法名。JavaBeans读方法仅返回值,而写方法使用值作为唯一的参数,因此我规定方法的参数类型以进行匹配。最后,该惯例要求方法为公共,因此我使用查找格式,查找类上可调用的公共方法。
这一实例是第一个我使用反射传递主值的实例,因此现在我们来看看它是如何工作的。基本原理很简单:无论什么时候您需要传递主值,只需用相应封装类的一个实例(在 java.lang
包中定义)来替换该类主值。这可以应用于调用和返回。因此,当我在实例中调用 get
方法时,我预计结果为实际 int
属性值的 java.lang.Integer
封装。
反射数组
数组是Java编程语言中的对象。与所有对象一样,它们都有类。如果您有一个数组,使用标准 getClass
方法,您可以获得该数组的类,就象任何其它对象一样。但是, 不通过现有的实例来获得类不同于其它类型的对象。即使您有一个数组类,您也不能直接对它进行太多的操作 -- 反射为标准类提供的构造函数接入不能用于数组,而且数组没有任何可接入的字段,只有基本的 java.lang.Object
方法定义用于数组对象。
数组的特殊处理使用 java.lang.reflect.Array
类提供的静态方法的集合。该类中的方法使您能够创建新数组,获得数组对象的长度,读和写数组对象的索引值。
清单5显示了一种重新调整现有数组大小的有效方法。它使用反射来创建相同类型的新数组,然后在返回新数组之前,在老数组中复制所有数据。
清单 5:通过反射来扩展一个数组
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;
}
|
安全性和反射
在处理反射时安全性是一个较复杂的问题。反射经常由框架型代码使用,由于这一点,您可能希望框架能够全面接入您的代码,无需考虑常规的接入限制。但是,在其它情况下,不受控制的接入会带来严重的安全性风险,如当代码在不值得信任的代码共享的环境中运行时。
由于这些互相矛盾的需求,Java编程语言定义一种多级别方法来处理反射的安全性。基本模式是对反射实施与应用于源代码接入相同的的限制:
- 从任意位置到类公共组件的接入
- 类自身外部无任何到私有组件的接入
- 受保护和打包(缺省接入)组件的有限接入
不过-至少某些时候,围绕这些限制有一种简单的方法。我在前面实例中使用的 Constructor
、 Field
和 Method
类都扩展了一个普通的基本类--  java.lang.reflect.AccessibleObject
类。该类定义一种 setAccessible
方法,使您能够启动或关闭对这些类中其中一个类的实例的接入检测。唯一的问题在于如果使用了安全性管理器,它将检测正在关闭接入检测的代码是否许可了这样做。如果未许可,安全性管理器抛出一个例外。
清单6展示了一个程序,在 清单 1 TwoString
类的一个实例上使用反射来显示安全性正在运行:
清单 6:反射安全性正在运行
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);
}
}
}
|
如果您编译了这一程序,不使用任何特定参数直接从命令行运行,它将在 field.get(inst)
调用中抛出一个 IllegalAccessException
。如果您未注释 field.setAccessible(true)
代码行,那么重新编译并重新运行该代码,它将取得成功。
最后,如果您在命令行添加了JVM参数 -Djava.security.manager
以实现安全性管理器,它将再次失败,除非您定义了 ReflectSecurity
类的许可权限。
反射是一种强大的工具,但也存在一些不足。一个主要的缺点是对性能有影响。使用反射基本上是一种解释操作,您可以告诉JVM您希望做什么并且它满足您的要求。这类操作总是慢于只直接执行相同的操作。为了阐述使用反射的性能成本,我为本文准备了一组基准程序(见 参考资料,完整代码链接)。
清单7是字段接入性能测试的一个摘用,包括基本的测试方法。每种方法测试字段接入的一种形式 -- accessSame
与同一对象的成员字段协作, accessOther
使用可直接接入的另一对象的字段, accessReflection
使用可通过反射接入的另一对象的字段。在每种情况下,方法执行相同的计算 -- 循环中简单的加/乘顺序。
清单 7:字段接入性能测试代码
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;
}
}
|
测试程序重复调用每种方法,使用一个大循环数,从而平均多次调用的时间衡量结果。平均值中不包括每种方法第一次调用的时间,因此初始化时间不是结果中的一个因素。在为本文进行的测试中, 每次调用时我使用1000万的循环数,在1GHz PIIIm系统上运行。三个不同Linux JVM的计时结果如图1所示。所有测试使用每个JVM的缺省设置。
图 1:字段接入时间
上表的对数尺度可以显示所有时间,但减少了差异看得见的影响。在前两副图中(Sun JVM),使用反射的执行时间超过使用直接接入的1000倍以上。通过比较,IBM JVM可能稍好一些,但反射方法仍旧需要比其它方法长700倍以上的时间。任何JVM上其它两种方法之间时间方面无任何显著差异,但IBM JVM几乎比Sun JVM快一倍。最有可能的是这种差异反映了Sun Hot Spot JVM的专业优化,它在简单基准方面表现得很糟糕。
除了字段接入时间测试之外,我还进行了相同的方法调用时间测试。在方法调用中,我试用了与字段接入相同的三种接入变量,并增加了使用无参数方法变量,而不是在方法调用中传递和返回一个值。清单8显示了用于测试调用传递和返回值形式的三种方法的代码。
清单 8:方法接入性能测试代码
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;
}
}
|
图 2显示了我从方法调用中获得的计时结果。反射远慢于直接接入。差异不象字段接入那么大,但是,在不使用参数的情况下,范围从Sun 1.3.1 JVM的数百倍到IBM JVM的不到30倍。在所有JVM上,使用参数的反射方法调用的测试性能慢于不使用参数的调用。由于传递和返回 int
值需要的 java.lang.Integer
封装,这可能是局部的。由于 Integer
s是不可变的,每种方法返回提出了一种新的需求,它将增加大量的开销。
图 2:方法调用时间
反 射性能是Sun开发1.4 JVM时关注的一个方面,它在反射方法调用结果中显示。在这类操作的性能方面,Sun 1.4.1 JVM显示了比1.3.1版本很大的改进,在我的测试中运行速度大约是1.3.1版本的开部。在这类简单的测试中,IBM 1.4.0 JVM再次获得了更好的成绩,但是只比Sun 1.4.1 JVM快两到三倍。
我还为创建使用反射的对象编写了类似的计时测试程序,但这种情况下的差异不象字段和方法调用情况下那么显著。使用 newInstance()
调用创建一个简单的 java.lang.Object
实例耗用的时间大约是在
Sun 1.3.1 JVM上使用 new Object()
的12倍,是在IBM 1.4.0 JVM的四倍,只是Sun 1.4.1 JVM上的两部。使用 Array.newInstance(type, size)
创建一个数组耗用的时间是任何测试的JVM上使用 new type[size]
的两倍,随着数组大小的增加,差异逐步缩小。
结束语
Java 语言反射提供一种动态链接程序组件的多功能方法。它允许程序创建和控制任何类的对象(根据安全性限制),无需提前硬编码目标类。这些特性使得反射特别适用 于创建以非常普通的方式与对象协作的库。例如,反射经常在持续存储对象为数据库、XML或其它外部格式的框架中使用。
反 射有两个缺点。第一个是性能问题。当用于字段和方法接入时反射要远慢于直接代码。性能问题的程度取决于程序中是如何使用反射的。如果它作为程序运行中相对 很少涉及的部分,缓慢的性能将不会是一个问题。即使测试中最坏情况下的计时图显示的反射操作只耗用几微秒。仅反射在性能关键的应用的核心逻辑中使用时性能 问题才变得至关重要。
许多应用更严重的一个缺点是使用反射会模糊程序内部实际要发生的事情。程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术会带来维护问题。反射代码比相应的直接代码更复杂,正如性能比较的代码实例中看到的一样。解决这些问题的最佳方案是保守地使用反射-- 仅在它可以真正增加灵活性的地方 -- 记录其在目标类中的使用。
在下一部分,我将提供如何使用反射的更详细实例。这种实例提供一个处理Java应用命令行参数的API,一种您可能发现适用于自己应用的工具。它还基于反射的优势来创建,同时避免其弱点。反射是否能简化您的命令行处理?您可以在 Java编程的动态性第3部分找到答案。
分享到:
相关推荐
这篇资料“提高Java程序动态性的一个新途径”可能探讨了如何通过各种技术手段来增强Java应用程序的动态特性。下面我们将深入讨论一些可能涉及的知识点。 1. **Java反射机制**:Java反射API允许我们在运行时检查类、...
《洪恩JAVA编程之道》是一本深入探讨Java编程技术的书籍,其随碟代码提供了丰富的实践示例,帮助读者巩固理论知识,提升编程技能。在Java编程的世界里,理解和掌握这些知识点至关重要,因为它们构成了Java程序员的...
Java反射机制是Java编程语言中一个强大的特性,它允许程序在运行时动态地访问、检测和修改类、接口、字段和方法等对象。然而,反射操作通常会引入额外的开销,这在性能敏感的应用场景下可能成为一个瓶颈。本文将深入...
Java反射在编程中是一种强大的工具,它允许程序在运行时检查和操作类、接口、字段和方法的信息。...在大多数情况下,优先考虑非反射设计,只有在必要的时候才引入反射技术,同时采取优化措施,以保持程序的高效运行。
Java编程模式与范例是Java开发者提升技能的重要资源,尤其在高级应用开发中,它们扮演着关键角色。本文将深入探讨这些模式和范例,并详细阐述如何在实际项目中运用它们,以提升代码质量、可维护性和扩展性。 1. **...
Java编程模式与范例是学习Java开发不可或缺的一部分,它们提供了高效、可维护的代码编写指导。在Java的世界里,模式和范例不仅是经验的总结,也是解决常见问题的标准方法。以下是一些关于Java编程模式与基础开发技巧...
Java动态性是Java编程语言中的一个重要特性,它使得程序在运行时可以改变其结构,比如添加、删除或修改类和对象的行为。这一特性使得Java在处理复杂和灵活的问题时具有强大的能力,尤其在开发框架、插件系统以及元...
Java编程语言是面向对象的、跨平台的编程语言,由Sun Microsystems公司于1995年推出,目前广泛应用于各种领域,如Web开发、移动应用、企业级应用、大数据处理等。"Java编程200例(附:JAVA文档完全解读中文版)"是一...
Java反射是Java编程语言中的一个重要特性,它允许运行时的Java程序访问并操作类、接口、字段和方法等对象的内部信息,即使这些信息在编译时并不知道。这一机制使得Java具有了高度的动态性,能够实现元编程,即在程序...
在Java编程语言中,反射是一个强大的工具,它允许程序在运行时检查类、接口、字段和方法的信息,甚至能够在运行时创建和访问这些对象。这种能力使得Java成为一种动态语言,增强了代码的灵活性和可扩展性。本文将深入...
通过反射,我们可以访问类、接口、字段和方法,甚至可以在运行时创建和访问对象,这为Java编程提供了极大的灵活性。 在Java中,反射主要通过`java.lang.reflect`包中的几个核心类来实现: 1. `Class<T>`:表示运行...
通过《Java核心技术(卷2):高级特性(原书第9版)》的学习和38套java高级架构视频教程的实践,开发者能够深入理解并熟练运用这些高级特性,从而提升自己的Java编程能力,构建更高效、稳定的应用程序。
总的来说,Java反射机制和动态代理是Java平台强大而灵活的特性,它们使得Java程序能够在运行时具有更高的灵活性和可扩展性。然而,使用反射也可能带来性能开销和安全风险,因此在实际应用中需要权衡利弊,合理使用。
10. **Java高级特性**:包括反射、动态代理、注解、枚举、集合工厂方法、泛型等,这些都是Java2平台的进阶特性,它们极大地增强了Java的灵活性和可维护性。 通过阅读《Java2编程详解》,读者可以系统地学习和掌握...
2. **异常处理**:Java提供了异常处理机制,通过try-catch-finally语句块来捕获和处理程序运行时可能出现的错误,确保程序的健壮性。 3. **集合框架**:Java集合框架包括List、Set、Queue和Map等接口,以及...
Java反射机制是Java编程语言中的一个强大特性,它允许程序在运行时检查和操作类、接口、对象等的内部结构。通过反射,开发者可以动态地创建对象、调用方法、访问字段,甚至修改私有成员,这为实现元编程和框架设计...
在Java中,反射机制是一种非常重要的特性,它使得程序能够在运行时动态地获取类的内部信息,如类名、构造器、成员变量和方法等,并且能够直接操作这些内部结构。通过这种方式,Java反射为开发者提供了极高的灵活性,...
- 动态性:可以在运行时决定创建哪个类的对象,并调用其方法。 - 灵活性:能够根据不同的输入参数创建不同的对象或执行不同的操作。 2. **局限性** - 性能开销:反射涉及很多查找和解析操作,相对于直接访问...
Java反射是Java编程语言中的一个核心特性,它允许运行中的Java程序对自身进行检查并且可以直接操作程序的内部属性。在Java中,反射机制主要涉及类的动态加载、对象的动态创建以及方法的动态调用等。这个名为"fanshe_...
7. **模块系统**:Java 9引入了模块系统(Project Jigsaw),旨在提高程序的可维护性和安全性。书中会介绍模块的概念、模块化项目的构建以及模块间的依赖关系。 8. **垃圾收集与内存管理**:Java的自动内存管理是...