跳转到内容

反射概述

75% developed
来自维基教科书,开放的书籍,为开放的世界

导航 反射 主题: v  d  e )

反射是 Java 在运行时公开类功能的机制,允许 Java 程序枚举和访问类的 方法、字段和构造函数作为对象。换句话说,存在反映 Java 对象模型的基于对象的 *镜像*,您可以使用这些对象在运行时使用 API 结构访问对象的属性,而不是编译时语言结构。每个对象实例都有一个 getClass() 方法,继承自 java.lang.Object,它返回一个对象,该对象包含该对象类的运行时表示;此对象是 java.lang.Class 的实例,进而具有返回该类的字段、方法、构造函数、超类和其他属性的方法。您可以使用这些反射对象访问字段、调用方法或实例化实例,所有这些都无需在编译时依赖这些功能。Java 运行时提供用于反射的相应类。大多数支持反射的 Java 类都位于 java.lang.reflect 包 中。反射对于执行 Java 的动态操作非常有用 - 这些操作不是硬编码到源程序中的,而是在运行时确定的。反射最重要的方面之一是 动态类加载

示例:调用 main 方法

[edit | edit source]

了解反射工作原理的一种方法是使用反射来模拟 Java 运行时环境 (JRE) 如何加载和执行类。当您调用 Java 程序时

Standard input or output 控制台

java 完全限定的类名 arg0 ... argn

并向它传递命令行参数,JRE 必须

  1. 将命令行参数 arg0 ... argn 放入 String[] 数组中
  2. 动态加载由 完全限定的类名 指定的目标类
  3. 访问 public static void main(String[]) 方法
  4. 调用 main 方法,将字符串数组 main String[] 传递给它。

步骤 2、3 和 4 可以使用 Java 反射完成。以下是如何加载 Distance 类、定位 main 方法(参见 理解 Java 程序)并通过反射调用它的示例。

Example 代码部分 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);
}

这段代码显然比简单地调用更复杂

Example 代码部分 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 以指示数组是参数,而不是参数在数组中。有关此内容的更多详细信息,请参见可变参数。

Example 代码部分 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)引发异常。

除了这些异常之外,这些方法还可能引发错误和运行时异常。


Clipboard

待办事项
添加一些类似于 变量 中的练习

华夏公益教科书