5.7 java之反射(获取class对象,分析类对象的成员变量、构造器、普通方法、动态分析类对象的值,编写泛型数组,调用任意方法)

0.什么是反射?

  • 首先我们先了解一下反射库,反射库(reflection library)提供了一个非常丰富且精心设计的工具集, 以便编写能够动态操纵 Java 代码的程序
  • 使用反射,在设计或运行中添加新类时, 能够快速地应用开发工具动态地查询新添 加类的能力。

能够分析类能力的程序称为反射(reflective)

反射机制可以用来:

  • 在运行时分析类的能力。
  • 在运行时查看对象, 例如, 编写一个 toString方法供所有类使用。
  • 实现通用的数组操作代码。
  • 利用 Method 对象, 这个对象很像C++中的函数指针

反射是一种功能强大且复杂的机制。使用它的主要人员是工具构造者,而不是应用程序员。

1.获取class类对象

  • 在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。 这个信息跟踪着每个对象所属的类。 虚拟机利用运行时类型信息选择相应的方法执行。
  • 然而,可以通过专门的 Java 类访问这些信息保存这些信息的类被称为 Class

获得Class类对象的三种方法:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 在启动时, 包含 main 方法的类被加载。它会加载所有需要的类。这些被加栽的类 又要加载它们需要的类, 以此类推。对于一个大型的应用程序来说, 这将会消耗很多时 间, 用户会因此感到不耐烦。
  • 可以使用下面这个技巧给用户一种启动速度比较快的幻觉。 不过,要确保包含 main 方法的类没有显式地引用其他的类。
  • 首先,显示一个启动画面; 然后,通过调用 Class.forName 手工地加载其他的类。

虚拟机为每个类型管理一个 Class 对象。因此, 可以利用= 运算符实现两个类对象比较 的操作。 例如:

在这里插入图片描述
还有一个很有用的方法 newlnstance( ), 可以用来动态地创建一个类的实例例如:

在这里插入图片描述

创建了一个与 e具有相同类类型的实例。newlnstance方法调用默认的构造器(没有参数的构 造器)初始化新创建的对象。如果这个类没有默认的构造器, 就会抛出一个异常。

将 forName 与 newlnstance 配合起来使用, 可以根据存储在字符串中的类名创建一个对象
在这里插入图片描述

2.初识捕获异常

  • 我们将在第 7 章中全面地讲述异常处理机制,但现在时常遇到一些方法需要抛出异常。
  • 当程序运行过程中发生错误时, 就会“ 抛出异常’抛出异常比终止程序要灵活得多, 这是因为可以提供一个“ 捕获” 异常的处理器(handler)对异常情况进行处理, 如果没有提供处理器,程序就会终止,并在控制台上打印出一条信息, 其中给出了异常的 类型。可能在前面已经看到过一些异常报告, 例如, 偶然使用了 null 引用或者数组越界等。
  • 异常有两种类型: 未检查异常和已检查异常
    对于已检查异常, 编译器将会检查是否提供了处理器。
    然而,有很多常见的异常, 例如,访问 null 引用, 都属于未检查异常。编译 器不会查看是否为这些错误提供了处理器。
    毕竟,应该精心地编写代码来避免这些错误的发 生, 而不要将精力花在编写异常处理器上。 并不是所有的错误都是可以避免的。
    如果竭尽全力还是发生了异常, 编译器就要求提供 一个处理器。

现在, 只介绍一下如何实现最简单的处理器。
将可能抛出已检查异常的一个或多个方法调用代码放在 try块中,然后在 catch 子句中提 供处理器代码。

在这里插入图片描述

  • 如果类名不存在, 则将跳过 try块中的剩余代码,程序直接进人 catch 子句(这里,利用 Throwable 类的 printStackTrace 方法打印出栈的轨迹。Throwable 是 Exception 类的超类) 。
  • 如果try块中没有抛出任何异常, 那么会跳过 catch 子句的处理器代码。
  • 对于已检查异常,只需要提供一个异常处理器。可以很容易地发现会抛出已检查异常的 方法。如果调用了一个抛出已检查异常的方法,而又没有提供处理器,编译器就会给出错误 报告

在这里插入图片描述

3.利用反射分析类的能力,获取成员变量、构造器、方法的结构和类型

我们可以利用反射机制检查类的结构。
下面介绍一下reflect包中的类和方法来分析类的能力:

  • 在java.lang.reflect 包中有三个类 Field、Method 和 Constructor分别用于描述类的域、 方 法和构造器
    这三个类都有一个叫做 getName 的方法, 用来返回项目的名称。
    还有一个叫 做getModifiers的方法, 它将返回一个整型数值,用不同的位开关描述 public 和 static 这样 的修饰符使用状况。

  • 可以利用java.lang.reflect 包中的 Modifiei类的静态方法分析getModifiers返回的整型数值。
    例如:可以使用Modifiei类中的 isPublic、 isPrivate 或 isFinal 判断方法或构造器是否是 public、 private 或 final。
    我们需要做的全部工作就是调用 Modifier 类的相应方法,并对返回的整型数值进行分析,另外,还可以利用 Modifier.toString方法将 修饰符打印出来

  • Field 类有一 个 getType 方法, 用来返回描述域所属类型的 Class 对象。

  • Method 和 Constructor 类有能够报告参数类型的方法
    Method 类有一个可以报告返回类型的方法。

  • Class类中的 getFields、 getMethods 和 getConstructors方 法将 分 别 返 回 类 提 供 的 public 域、 方法和构造器数组, 其中包括超类的公有成员

  • Class 类的 getDeclareFields、 getDeclareMethods 和 getDeclaredConstructors方法将分别返回类中声明的全部域、 方法和构 造器, 其中包括私有和受保护成员,但不包括超类的成员。

下面是一个代码案例,显示了如何打印一个类的全部信息的方法。
这个程序提醒用户输入一个类名,然后输出类中所有的方法和构造器签名,以及全部域名。

在这里插入图片描述

输出结果:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.在运行时使用反射分析对象,获取类对象的成员变量值和类型

在编写程序时, 如果知道想要查看的域名 和类型,查看指定的域是一件很容易的事情。而利用反射机制可以查看在编译时还不清楚的对象域

查看对象域的关键方法是 Field类中的 get 方法。如果 f 是一个 Field类型的对象(例如, 通过 getDeclaredFields 得到的对象),obj 是某个包含 f 域的类的对象,f.get(obj) 将返回一个 对象,其值为 obj 域的当前值。

在这里插入图片描述
实际上,这段代码存在一个问题。由于 name 是一个私有域, 所以 get 方法将会抛出一个IllegalAccessException。只有利用 get 方法才能得到可访问域的值。除非拥有访问权限,否则 Java 安全机制只允许查看任意对象有哪些域, 而不允许读取它们的值。

反射机制的默认行为受限于 Java 的访问控制。然而, 如果一个 Java 程序没有受到安全管理器的控制, 就可以覆盖访问控制。 为了达到这个目的, 需要调用Field、Method 或 Constructor 对象的 setAccessible 方法。例如,

在这里插入图片描述
setAccessible方法是 AccessibleObject 类中的一个方法, 它是 Field、 Method 和 Constructor 类的公共超类。这个特性是为调试、持久存储和相似机制提供的。

get方法还有一个需要解决的问题。name 域是一个 String, 因此把它作为 Object 返回 没有什么问题。但是, 假定我们想要查看salary 域。它属于 double 类型,而 Java中数值类 型不是对象。要想解决这个问题, 可以使用 Field 类中的 getDouble方法,也可以调用 get 方法,此时, 反射机制将会自动地将这个域值打包到相应的对象包装器中,这里将打包成 Double

当然,可以获得就可以设置。调用f.set(obj,value) 可以将 obj 对象的 f 域设置成新值

下图显示了如何编写一个可供任意类使用的通用 toString方法。 其中使用 getDeclaredFileds 获得所有的数据域, 然后使用 setAccessible 将所有的域设置为可访问的。 对 于每个域,获得了名字和值。递归调用 toString方法,将每个值转换成字符串。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
泛型 toString方法需要解释几个复杂的问题。循环引用将有可能导致无限递归。因此, ObjectAnalyzer 将记录已经被访问过的对象。另外, 为了能够查看数组内部, 需要采用一种 不同的方式

还可以使用通用的 toString 方法实现ft 己类中的 toString 方法, 如下所示:
在这里插入图片描述
这是一种公认的提供 toString 方法的手段, 在编写程序时会发现, 它是非常有用的。

5.使用反射编写泛型数组代码,复制数组

  • 可以利用反射编写一个通用的扩充任意类型数组长度的方法,类似copyOf()
  • 这里需要注意的一点是:将一个Employee[]数组临时的转换为Object[],然后再转换回来是可以的,但是从一开始就是Object[]数组却永远不能转换成Employee[]数组。(强制类型转换),联想父类子类的转换。
    在这里插入图片描述在这里插入图片描述

6.利用反射库调用任意方法

  • 反射机制可以允许调用任意的方法
  • 在Method类中有一个invoke方法,它可以允许调用包装在Method对象中的方法
    在这里插入图片描述
    在这里插入图片描述
    下面是一个代码实例:

在这里插入图片描述在这里插入图片描述
参考:《java核心技术卷一》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章