Java 框架的核心 -- 反射

Java程序在运行时都会出现两种类型:编译时类型运行时类型

例如代码:Person p = new Student(); 变量 p 的编译时类型为 Person,而运行时类型为 Student。

这些变量编译时类型和运行时类型不一致的,程序员需要在运行时发现对象和类的真实信息,解决这个问题有两种做法:

  1. 假设在编译时和运行时都完全知道类型的具体信息。可以先用 instanceof 运算符进行判断,再进行强制转换。
  2. 若编译时根本无法预知该对象和类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息,就必须用到反射

获得 Class 对象

每个类被加载后,系统就会为该类生成一个对应的 Class 对象,通过该 Class 对象就可以访问到 JVM 中的这个类。

获取 Class 对象通常有三种方式:

  1. 使用 Class 类的 forName(String clazzName) 静态方法。参数为某个类的全限定类名。
  2. 调用某个类的 class 属性来获取该类对象的 Class 对象。例如,Person.class 将会返回 Person 类的 Class 对象。
  3. 调用某个类的 getClass() 方法。

第一种和第二种方式都是直接根据类来获取,不过相比之下,第二种方式更有优势。

所以一般情况下,都是通过类的 class 属性获取指定类的 Class 对象。如果只能获得一个字符串,就只能用第一种方式了。

一旦取得了某个类的 Class 对象之后,程序就可以调用 Class 对象的方法来获得该对象的真实信息了!

从 Class 中获取信息

Class 类提供了大量的实力方法来获取该 Class 对象所对应类的详细信息,大致如下。而每个方法包括多个重载,详细应该去 查阅 API。该类位于 java.lang.Class。

获取 Class 对应类所包含的构造器:

  • Constructor<T> getConstructor(Class<?>... parameterTypes) :返回指定形参列表的 public 修饰的构造器。
  • Constructor<?>[] getConstructors() :返回所有 public 构造器。
  • Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) :返回指定形参列表的构造器,与构造器的访问权限无关。
  • Constructor<?>[] getDeclaredConstructors() :返回所有 public 构造器,与构造器的访问权限无关。

获取 Class 对应类所包含的方法:

  • Method getMethod(String name, Class<?>... parameterTypes) :返回该类指定形参列表的 public 方法。
  • Method[] getMethods() :返回该类所有的 public 方法。
  • Method getDeclaredMethod(String name, Class<?>... parameterTypes) :返回该类指定形参列表的方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
  • Method[] getDeclaredMethods() :返回该类所有的方法,与方法的访问权限无关,但不包括继承的方法。

访问 Class 对应类所包含的成员变量:

  • Field getField(String name) :返回指定名称的 public 成员变量。
  • Field[] getFields() :返回所有 public 成员变量数组。
  • Field getDeclaredField(String name) :返回指定名称的成员变量,与成员变量的访问权限无关。
  • Field[] getDeclaredFields() :返回所有成员变量数组,与成员变量的访问权限无关。

。。。

。。

方法太多了,看着有点繁琐。还是自己有空去看看 API 文档吧,该类位于 java.lang.Class。


使用反射生成并操作对象

Class 对象可以获得该类里的方法(Method 对象)、构造器(Constructor 对象)、成员变量(Field 对象),这三个类都位于 java.lang.reflect 包下。

程序可以通过 Method 对象来执行对应的方法,通过 Constructor 对象来调用对应的构造器创建实例,能通过 Field 对象直接访问并次改对象的成员变量值。

创建对象

通过反射来创建对象有如下两种方式:

  • 使用 Class 对象的 newInstance() 方法创建实例。这种方式要求该 Class 对象的对应类有默认构造器,通过默认构造器创建实例。
  • 先使用 Class 对象索取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance() 方法创建实例。这种方式可以选择使用指定的构造器来创建实例。

通过第一种方式来创建对象是比较常见的。很多Java EE 框架中都需要根据配置文件信息来创建对象,从配置文件读取某个类的字符串类名,然后通过反射生成实例。

举例

使用第一种方式创建对象

// 传入一个字符串类名,根据类名生成 Java 对象
private Object createObject(String calzzName) throws Exception{
    // 根据字符串来获取对应的 Class 对象
    Class<?> calzz = Class.forName(calzzName);
    // 使用 calzz 对应类的默认构造器创建对象
    return calzz.newInstance();
}

使用第二种方式创建对象

public class CreateJFrame{
    public static void main(String[] args) throws Exception{
        // 获取 JFrame 对应的 Class 对象
        Class<?> jframeClazz = Class.forName("javax.swing.JFrame");
        // 获取 JFrame 中带一个字符串参数的构造器
        Constructor ctor = jframeClazz.getConstructor(String.calzz);
        // 调用 newInstance 方法创建对象
        Object obj = ctor.newInstance("测试窗口");
        //输出对象
        System.out.println(obj);
    }
}

对于上面的 CreateJFrame.java 中已知 javax.swing.JFrame 类的情形,通常没必要使用反射来创建该对象,毕竟通过反射创建对象时性能要稍微低一些。

实际上,只有当程序需要动态创建某个类的对象时,才需要考虑使用反射,通常在开发通用性比较广的框架、基础平台时可能会大量使用反射。

调用方法

当通过 Class 对象的 getMethod() 方法或者 getMethods() 获取方法后,就得到了 Method 对象或对象数组,每个 Method 对象对应一个方法。

得到 Method 对象后,程序就可通过 Method 的 invoke() 方法来调用 Method 对应的方法。

  • invoke(Object obj, Object... args) :该方法中的 obj 是执行该方法的主调,args 是执行该方法时传入的实参。

访问成员变量

通过 Class 对象的 getFields() 或 getField() 方法可以获取该类所包含的全部成员变量或指定成员变量。

Field 提供了如下两组方法来读取或设置成员变量值:

  • getXxx(Object obj) :获取 obj 对象的该成员变量的值。此处的 Xxx 对应 8 种基本类型,如果成员变量的类型是应用类型,则取消 get 后面的 Xxx。
  • setXxx(Object obj, Xxx val) :将 obj 对象的该成员变量设置成 val 值。此处的 Xxx 对应 8 种基本类型,如果成员变量的类型是应用类型,则取消 get 后面的 Xxx。

使用这两个方法可以随意地访问指定对象的所有成员变量,包括 private 修饰的成员变量。


小结

反射的内容,对于使用 Java 不多的人来说,显得有点不太实用了。但是随着开发经验慢慢积累,会发现在很多时候使用反射可以解决很多问题。特别是在 Spring 框架中,了解反射之后去使用和阅读 Spring 会轻松很多。

不过反射涉及了非常多 API,这里只是简单介绍了一些常用的,想深入了解还是要针对性去看看 API 接口文档。

posted @ 2021-07-14 11:14  乐子不痞  阅读(88)  评论(0编辑  收藏  举报
回到顶部