深入浅出!全面剖析 Java 反射 -Reflection,java 项目开发实战入门电子书百度云
动态
那么所谓动态是指,虚拟机并不是一次性把全部 Class 文件都加载到内存中,而是在第一次需要用到时才加载。举个例子,假设我们编写包含 A、B、C、D、E 五个类的程序,其中类 A 是程序入口且调用了类 B,类 B 调用了类 C,类 C 在某些情况下调用了类 D,类 E 跟其他类没有调用关系。以下是执行该程序的步骤
编译程序后,磁盘中生成了五个类的二进制文件。
运行程序,那么虚拟机会先将 A.class 文件加载进内存,同时利用这个二进制文件生一个 java.lang.Class 类的实例,该实例用于描述类 A 的全部信息。
在执行类 A 期间,发现其调用了类 B。那么虚拟机将 B.class 文件加载进内存,并生成对应的 java.lang.Class 类的实例。
同样地,执行 B 期间调用了类 C。虚拟机将 C.class 文件加载进内存,并生成对应的 java.lang.Class 类的实例。
假设初始情况下,类 C 不调用类 D。那么虚拟机并不会将 D.class 文件加载进内存。以上步骤发生在程序运行期间,是程序根据类间调用关系完成首次的类文件的动态加载,即需要哪个类就加载其对应的 Class 文件。
随着程序的运行,类 C 需要调用类 D,此时虚拟机会从磁盘中将 D.class 加载进内存,并生成对应的 java.lang.Class 类的实例,然后供类 C 调用。此处也体现了动态加载的思想,即在程序运行过程中,虚拟机仍可根据需要加载 Class 文件。
在整个程序运行过程中,由于类 E 不同其他类交互,不存在被调用的机会,因此 E.class 文件会一直呆在磁盘中,不会被虚拟机加载到内存中。
定义
反射(Reflection) 是 Java 在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。通俗地讲,有了 java 提供的反射机制,我们可在运行时拿到一个对象所属类的全部信息。
从定义可知,反射是 java 提供给开发者操作业务类的一种能力,而这种能力是基于 java.lang.Class 类型。java.lang.Class 类提供了众多方法,譬如 getField()、getMethod()等等,正是这些方法赋予了开发者反射定义中所描述的能力。
定义中给出了反射发挥作用所处的时间段是在程序运行时,这是因为程序开始运行之后,虚拟机会按需把普通类的类文件动态加载进内存,然后虚拟机就可以拿到这些普通类对应的类文件进而实现反射所描述的功能。这个“按需”的本质含义是类加载的时机,可参考《深入理解 java 虚拟机》7.2 小节。
java.lang.Class 类型
java.lang.Class 类是一种类类型,java.lang.Class 实例用于存储对应的普通类的全部的描述信息。每一个 java.lang.Class 的实例都对应一个普通类,且是一对一的关系,跟普通类的实例数量无关。
创建 Class 类型实例
java.lang.Class 类的构造函数是私有的,不能使用 new 创建 java.lang.Class 实例,只能由虚拟机根据加载的 class 文件创建。正如“动态加载 Class 文件”小节所描述,虚拟机会根据需要,动态加载所需类的 Class 文件到内存中。这个 Class 文件虽然是二进制文件,但是它符合虚拟机规范,因此虚拟机可以识别它所代表的含义。虚拟机拿到 Class 文件之后,便根据这个文件在内存中创建一个用于描述普通类的 java.lang.Class 类型实例。
获取 Class 类型实例
虽然无法创建 java.lang.Class 实例,但是虚拟机提供了三种方法获取 java.lang.Class 对象。
实例.getClass()。由于 Object 类是所有 java 类的父类,且 Object 提供 getClass 方法,因此任何类都可以使用 getClass 方法获取对应的 Class 实例。
Person p = new Person();Class clazz = p.getClass();
类名.class。使用类的属性。
Class cla
zz = Person.class //Class 实例命名用 clazz
Class.forName(“普通类的全限定路径”)。这种方法多用于利用反射机制创建普通类实例。
Class clazz = Class.forName("com.guo.Person")
反射用这种方式是因为是在不清楚普通类的信息的前提下,根据类名创建 Class 实例,然后进行创建普通类实例等操作。注意此时只是知道普通类的路径名信息,该普通类的 Class 文件可能尚未加载到内存中,即在编译阶段尚未确定是否调用该类。而前两种方式都是先调用普通类,然后获取 Class 实例,这要求提前获取普通类的信息,与反射中事前不清楚类信息的场景不符合。
Class 类常用方法
Class 类提供许多方法,能够全面控制 Class 实例对应的普通类。本文从实现功能的角度,介绍一些 Class 类常用的方法。
以下方法都是由 Class 实例调用,操作结果对应 Class 实例对应的普通类。
获取普通类的构造方法
根据 Class 类实例访问其对应的普通类的构造方法,涉及以下方法
Constructor getConstructor(Class<?>… parameterTypes) 获取指定的 public 的构造器
Class<?>… parameterType 表示构造器的参数类型
参数的三个点表示可变参数列表,即该方法可以传入零到多个 Class 类型参数
譬如当构造器的参数是 String 类型时,此处应该传入 String.class
Constructor[] getConstructors() 获取全部 public 的构造器
Constructor getDeclaredConstrnctor(Class<?>… parameterTypes) 获取某个构造器,不分公有、私有
(Class<?>… parameterType 表示构造器的参数类型
譬如当构造器的参数是 String 类型时,此处应该传入 String.class
Constructor[] getDeclaredConstructors() 获取全部构造器,不分公有、私有
tips:
构造器总是作用在当前类,和父类无关,因此不存在多态问题。
加”Declared“表示获取当前类的全部构造器,不分公私。而不加“Declared”表示当前类的公有构造器。
加“s”表示获取全部,返回值是个数组。
以上方法的返回值类型是 Constructor 类,位于 java.lang.reflect 中。该类的构造器为私有,只能通过 Class 实例获取。用于描述普通类的构造器全部信息,包括修饰符、构造器名称、构造器参数。通过 Constructor 类提供的 getName()、getModifiers()等方法,我们可以获取一个类的构造器的名称、参数等信息。
Constructor 类提供了一个 public T newInstance(Object … initargs)方法,利用该方法可以根据指定的构造方法创建对应的普通类的一个实例,但创建实例时 newInstance 方法中的参数列表必须与获取 Constructor 实例的 getConstructor 方法中的参数列表一致。与 Constructor 类不同,Class 类也提供一个 newInstance()方法用于创建对应普通类的实例,但这个方法要求普通类必须有无参构造方法,实质上 Class 类的 newInstance()方法底层也是调用 Constructor 类的 newInstance()方法。类似如下代码
Class clazz = Class.forName("com.guo.Person")//使用 Class 实例创建实例,只能调用无参构造方法,因此要求普通类必须有无参构造方法。Person p1 = (Person) clazz.newInstance();
//获取指定构造函数的类实例 Constructor con = clazz.getConstructor(String.class, int.class);Person p2 = (Person) con.newInstance("lisi", 30);
获取普通类的字段
根据 Class 类实例访问其对应的普通类的字段信息,涉及以下方法
Field getField(name):根据字段名获取某个 public 的 Field 实例(包括父类)
Field[] getFields():获取全部 public 的 Field 实例(包括父类)
Field getDeclaredField(name):根据字段名获取当前类的的某个 Field 实例(不包括父类)
Field[] getDeclaredFields():获取当前类的全部 Field 实例
tips:
加”Declared“表示获取当前类的属性,包括公有、私有。
不加“Declared”表示获取当前类和父类的公有属性。
加“s”表示获取全部,返回值是个数组。
以上方法的返回值类型是 Field 类,位于 java.lang.reflect 中。该类的构造器为私有,只能通过 Class 实例获取。用于描述类或接口的字段的全部信息,包括修饰符、字段类型、字段名称。通过 Field 类提供的 getName()、getType()、set(Object,Object)、setAccessible(Boolean)等方法,我们可以获取、修改一个类的字段的名称、值或访问权限。
获取普通类的方法
根据 Class 类实例访问其对应的普通类的方法信息,涉及以下方法
Method getMethod(String name,Class<?>… parameterTypes) 获取指定 public 的 Method 实例(包括父类)。
参数 name 为要获取的方法名
评论