反射概述
导航 反射 主题: ) |
反射是 Java 在运行时公开类功能的机制,允许 Java 程序枚举和访问类的 方法、字段和构造函数作为对象。换句话说,存在反映 Java 对象模型的基于对象的 *镜像*,您可以使用这些对象在运行时使用 API 结构访问对象的属性,而不是编译时语言结构。每个对象实例都有一个 getClass()
方法,继承自 java.lang.Object
,它返回一个对象,该对象包含该对象类的运行时表示;此对象是 java.lang.Class
的实例,进而具有返回该类的字段、方法、构造函数、超类和其他属性的方法。您可以使用这些反射对象访问字段、调用方法或实例化实例,所有这些都无需在编译时依赖这些功能。Java 运行时提供用于反射的相应类。大多数支持反射的 Java 类都位于 java.lang.reflect 包
中。反射对于执行 Java 的动态操作非常有用 - 这些操作不是硬编码到源程序中的,而是在运行时确定的。反射最重要的方面之一是 动态类加载。
示例:调用 main
方法
[edit | edit source]了解反射工作原理的一种方法是使用反射来模拟 Java 运行时环境 (JRE) 如何加载和执行类。当您调用 Java 程序时
控制台
java 完全限定的类名 arg0 ... argn |
并向它传递命令行参数,JRE 必须
- 将命令行参数 arg0 ... argn 放入
String
[] 数组中 - 动态加载由 完全限定的类名 指定的目标类
- 访问
public
static
void
main(String[])
方法 - 调用
main
方法,将字符串数组 mainString[]
传递给它。
步骤 2、3 和 4 可以使用 Java 反射完成。以下是如何加载 Distance
类、定位 main
方法(参见 理解 Java 程序)并通过反射调用它的示例。
代码部分 10.1:main() 方法调用。
public static void invokeMain()
throws ClassNotFoundException,
ExceptionInInitializerError,
IllegalAccessException,
IllegalArgumentException,
InvocationTargetException,
NoSuchMethodException,
SecurityException {
Class<?> distanceClass = Class.forName("Distance");
String[] points = {"0", "0", "3", "4"};
Method mainMethod = distanceClass.getMethod("main", String[].class);
Object result = mainMethod.invoke(null, (Object) points);
}
|
这段代码显然比简单地调用更复杂
代码部分 10.2:main() 方法调用。
Distance.main(new String[]{"0", "0", "3", "4"});
|
但是,主 Java 运行时不知道 Distance
类。要执行的类的名称是运行时值。反射允许 Java 程序使用类,即使在编写程序时不知道这些类。让我们探讨一下 invokeMain
方法在做什么。第 9 行的第一条语句是 动态类加载 的示例。forName()
方法将加载 Java 类并返回 java.lang.Class
的实例,该实例是加载类后产生的。在本例中,我们从默认包中加载类 "Distance"
。我们将类对象存储在名为 distanceClass
的局部变量中;它的类型是 Class
<?>。第 10 行的第二条语句只是创建一个带有四个命令行参数的 String
数组,我们希望将其传递给 Distance
类的 main
方法。第 11 行的第三条语句对 Distance
类执行反射操作。getMethod()
方法是为 Class
类定义的。它接受可变数量的参数:方法名称是第一个参数,其余参数是每个 main
参数的类型。方法名称很简单:我们希望调用 main
方法,因此我们将名称 "main"
传递给它。然后,我们为每个方法参数添加一个 Class
变量。main
接受一个参数 (String
[] args)
,因此我们添加一个表示 String[] 的单个 Class
元素。getMethod
方法的返回类型是 java.lang.reflect.Method
;我们将结果存储在一个名为 mainMethod
的局部变量中。最后,我们通过调用 Method
实例的 invoke()
方法来调用该方法。此方法的第一个参数是要调用它的实例,其余参数是调用者的参数。由于我们正在调用静态方法而不是实例方法,因此我们将 null
作为实例参数传递。由于我们只有一个参数,因此将其作为第二个参数传递。但是,我们必须将参数转换为 Object 以指示数组是参数,而不是参数在数组中。有关此内容的更多详细信息,请参见可变参数。
代码部分 10.3:invoke() 调用。
Object result = mainMethod.invoke(null, arguments);
|
invoke()
方法返回一个 Object
,它将包含反射方法返回的结果。在本例中,我们的 main
方法是一个 void
方法,因此我们忽略了返回类型。此简短 invokeMain
方法中的大多数方法可能会引发各种异常。该方法在其签名中声明了所有异常。以下是可能会引发异常的简要概述
Class.forName(String)
将引发ClassNotFoundException
,如果无法找到指定类。Class.forName(String)
将引发ExceptionInInitializerError
,如果由于静态初始化程序引发异常或静态字段的初始化引发异常而无法加载该类。Class.getMethod(String name, Class parameterTypes[])
将引发NoSuchMethodException
,如果找不到匹配的方法,或者该方法不是公共的(使用getDeclaredMethod
获取非公共方法)。SecurityException
,如果安装了安全管理器并且调用该方法会导致访问冲突(例如,该方法位于专为内部使用而设计的sun.*
包中)。
Method.invoke(Object instance, Object... arguments)
可能会引发IllegalAccessException
,如果以违反其访问修饰符的方式调用此方法。IllegalArgumentException
出于多种原因,包括- 传递未实现此方法的实例。
- 实际参数与方法参数不匹配
InvocationTargetException
,如果底层方法(本例中的main
)引发异常。
除了这些异常之外,这些方法还可能引发错误和运行时异常。