反射机制

Table of Contents

通过 Java 的反射机制,程序员可以更深入地控制程序的运行过程。例如,在程序运行时由用户输入一个类名,然后动态获取该类拥有的构造、属性和方法,甚至调用任意类的任意方法。

反射机制是什么

Java 反射机制是 Java 语言的一个重要特性。在学习 Java 反射机制前,我们先来了解两个概念:编译期和运行期。

编译期 是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能, 并没有 把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误。

运行期 是把编译后的文件交给计算机执行,直至程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。

Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意属性和方法;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制 。简单来说,反射机制指的是程序在运行时能够获取自身的信息。在 Java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。

Java 反射机制在服务器和中间程序中得到了广泛运用。在服务端,往往需要根据客户的请求,动态调用某一个对象的特定方法。此外,在 ORM 中间件的实现中,运用 Java 反射机制可以读取任意一个 JavaBean 的所有属性,或者给这些属性赋值。

Java 反射机制主要提供了以下功能,这些功能都位于 java.lang.reflect 包:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法;
  • 在运行时调用任意一个对象的方法;
  • 生成动态代理。

要想知道一个类的属性和方法,必须先获取该类的字节码文件对象。获取类的信息时,使用的就是 Class 类中的方法。所以,先要获取到每一个字节码文件( .class )对应的 Class 类型的对象。

我们知道, 所有 Java 类均继承了 Object 类 ,在 Object 类中定义了一个 getClass() 方法,该方法返回同一个类型为 Class 的对象,如下:

1: Class labelCls = label1.getClass(); // label1 为 JLabel 类的对象

利用 Class 类的对象 labelCls 可以访问 labelCls 对象的描述信息、 JLabel 类的信息以及基类 Object 的信息。

下表列出了通过反射可以访问的信息:

Table 1: 反射可访问的常用信息
类型 访问方法 返回值类型 说明
包路径 getPackage() Package 对象 获取该类的存放路径
类名称 getName() String 对象 获取该类的名称
继承类 getSuperclass() Class 对象 获取该类继承的类
实现接口 getInterfaces() Class 型数组 获取该类实现的所有接口
构造方法 getConstructors() Constructor 型数组 获取所有权限为 public 的构造方法
  getDeclaredContructors() Constructor 对象 获取当前对象的所有构造方法
方法 getMethods() Methods 型数组 获取所有权限为 public 的方法
  getDeclaredMethods() Methods 对象 获取当前对象的所有方法
成员变量 getFileds() Field 型数组 获取所有权限为 public 的成员变量
  getDeclaredFields() Field 对象 获取当前对象的所有成员变量
内部类 getClasses() Class 型数组 获取所有权限为 public 的内部类
  getDelaredClasses() Class 型数组 获取所有内部类
内部类的声明类 getDeclaringClass() Class 对象 如果该类为内部类,则返回它的成员类,否则返回 null

*注:上表中,在调用 getFileds()getMethods() 方法时将会依次获取权限为 public 的字段和变量,然后将包含从超类中继承到的成员变量和方法,而通过 getDeclaredFields()getDeclaredMethods() 中只是获取在本类中定义的成员变量和方法。

Java 反射机制的优点:

  • 能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性;
  • 与 Java 动态编译相结合,可以实现无比强大的功能;
  • 对于 Java 这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

Java 反射机制的缺点:

  • 反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
  • 反射调用方法时可以忽略权限检查,获取这个类的私有方法和属性,因此可能会破坏类的封装性而导致安全问题。

*注:Java 反射机制在一般的 Java 应用开发中很少使用,即便是 Java EE 阶段也很少使用。

反射机制 API

实现 Java 反射机制的类都位于 java.lang.reflect 包中, java.lang.Class 类是 Java 反射机制 API 中的核心类。

1. java.lang.Class 类

java.lang.Class 类是实现反射的关键所在,Class 类的一个实例表示 Java 的一种数据类型,包括类、接口、枚举、注解(Annotation)、数组、基本数据类型和 void 。Class 没有公有的构造方法,Class 实例是由 JVM 在类加载时自动创建的。

在程序代码中获得 Class 实例可以通过如下代码实现:

1: // 1. 通过类型 class 静态变量
2: Class clz1 = String.class;
3: // 2. 通过对象的 getClass() 方法
4: String str = "Hello";
5: Class clz2 = str.getClass();

每一种类型包括类和接口等,都有一个 class 静态变量可以获得 Class 实例。另外,每一个对象都有 getClass() 方法获得 Class 实例,该方法是由 Object 类提供的实例方法。

Class 类提供了很多方法可以获得运行时对象的相关信息,下面的程序代码展示了其中一些方法。

 1: public class ReflectionTest {
 2:     public static void main(String[] args) {
 3:         // 获得 Class 实例
 4:         // 1. 通过类型 class 静态变量
 5:         Class clz1 = String.class;
 6:         // 2. 通过对象的 getClass() 方法
 7:         String str = "Hello";
 8:         Class clz2 = str.getClass();
 9:         // 获得 int 类型的 Class 实例
10:         Class clz3 = int.class;
11:         // 获得 Integer 类型 Class 实例
12:         Class clz4 = Integer.class;
13: 
14:         System.out.println("clz2 类名称:" + clz2.getName());
15:         System.out.println("clz2 是否为接口:" + clz2.isInterface());
16:         System.out.println("clz2 是否为数组对象:" + clz2.isArray());
17:         System.out.println("clz2 父类名称:" + clz2.getSuperclass().getName());
18:         System.out.println("clz2 是否为基本类型:" + clz2.isPrimitive());
19:         System.out.println("clz3 是否为基本类型:" + clz3.isPrimitive());
20:         System.out.println("clz4 是否为基本类型:" + clz4.isPrimitive());
21: 
22:         // → clz2 类名称: java.lang.String
23:         // → clz2 是否为接口: false
24:         // → clz2 是否为数组对象: false
25:         // → clz2 父类名称: java.lang.Object
26:         // → clz2 是否为基本类型:false
27:         // → clz3 是否为基本类型:true
28:         // → clz4 是否为基本类型:false
29:     }
30: }
31: 

2. java.lang.reflect 包

java.lang.reflect 包提供了反射中用到的类,主要的类说明如下:

  • Constructor 类:提供类的构造方法信息;
  • Field 类:提供类或接口中成员变量信息;
  • Mehtod 类:提供类或接口成员方法信息;
  • Array 类:提供了动态创建和访问 Java 数组的方法;
  • Modifer 类:提供类和成员访问修饰符信息。

示例代码如下:

 1: public class ReflectionTest {
 2:     public static void main(String[] args) {
 3:         try {
 4:             // 动态加载 xx 类的运行时对象
 5:             Class c = Class.forName("java.lang.String");
 6:             // 获取成员方法集合
 7:             Method[] methods = c.getDeclaredMethods();
 8:             // 遍历成员方法集合
 9:             for (Method method : methods) {
10:                 // 打印权限修饰符,如 public、protected、private
11:                 System.out.println(Modifier.toString(method.getModifiers()));
12:                 // 打印返回值类型名称
13:                 System.out.println(method.getReturnType().getName());
14:                 // 打印方法名称
15:                 System.out.println(method.getName() + "()");
16:             }
17:         } catch (ClassNotFoundException e) {
18:             System.out.println("找不到指定类");
19:         }
20:     }
21: }

上述代码第 5 行是通过 Class 的静态方法 forName(String) 创建某个类的运行时对象,其中的参数是类全名字符串,如果在类路径中找不到这个类则抛出 ClassNotFoundException 异常,见代码第 17 行。

TODO 通过反射访问构造方法

TODO 通过反射访问方法

TODO 通过反射方法成员变量

Date: 2020-10-02 Fri 11:51

Author: Jack Liu

Created: 2020-10-02 Fri 15:08

Validate