今天分享Java学习的第七个专题——反射。
反射概述
Java 中的反射(Reflection)机制是指,Java 程序在运行期间可以获取到一个对象的全部信息。
反射机制一般用来解决Java 程序运行期间,对某个实例对象一无所知的情况下,如何调用该对象内部的方法问题。
反射机制允许 Java 程序在运行时调用Reflection API取得任何类的内部信息(比如成员变量、构造器、成员方法等),并能操作类的实例对象的属性以及方法。
在Java 程序中,JVM 加载完一个类后,在堆内存中就会产生该类的一个 Class 对象,一个类在堆内存中最多只会有一个 Class 对象,这个Class 对象包含了该类的完整结构信息,我们通过这个 Class 对象便可以得到该类的完整结构信息,将获取Class对象的过程称为:反射。
实现Java反射的基础
Java 提供反射机制,依赖于 Class 类和 java.lang.reflect 类库。其主要的类如下:
- Class:表示类或者接口
- Field:表示类中的成员变量
- Method:表示类中的方法
- Constructor:表示类的构造方法
- Array:该类提供了动态创建数组和访问数组元素的静态方法
Class类
在Java语言中,Class类是一个特殊的类,它代表运行时的类型信息。事实上,Java中的每一个类都与一个Class对象相关联,在编写并编译任何新类时,该类的信息被封装并保存到一个.class文件中。当我们通过关键字new创建一个对象实例或者访问一个类的静态成员时,Java虚拟机(JVM)的类加载器子系统会将相应的Class对象加载到JVM的内存空间中。紧接着,JVM利用这些加载的Class对象信息来实例化新的对象或者返回静态成员的引用。
我们通常将Class类称之为“类类型”(class type),而每个类关联的Class对象称之为“类类型对象”(class type object)。Class类具有一些特殊的特性:
- Class本身也是一个类,而class则是Java语言中的一个关键字。
- Class类有一个私有的构造函数,因而它的实例只能由JVM内部创建。
- 对于同一个类(即具有相同的完全限定名,并由相同的类加载器加载的类),在JVM中只存在一个唯一的Class对象来描述其类型信息。
.class文件包含了一个类的完整定义,包括它的所有方法、构造函数、字段(成员变量)等。当JVM启动时,它会通过读取.class文件来将对应的类加载到内存中。
在Class类的源码中,有一个私有构造器:
提供了几个获取类实例的方法:
(1)forName():
使用该方法可能会抛出 ClassNotFoundException 异常,这个异常发生在类的加载阶段,原因如下:
- 类加载器在类路径中没有找到该类(检查:查看所在加载的类以及其所依赖的包是否在类路径下)
- 该类已经被某个类加载器加载到 JVM 内存中,另外一个类加载器又尝试从同一个包中加载
(2)Object.getClass() 方法
如果我们有一个类的对象,那么我们可以通过 Object.getClass 方法获得该类的 Class 对象。
(3)class属性
总结一下,Class也是一个类,其类名就叫Class,因此它也继承 Object 类;
Class类对象不是由我们程序员创建(new)出来的,而是在类加载时由 JVM 自动创建的。
在堆内存中最多只会存在某个类的唯一的Class对象,因为类只会加载一次,每个类的实例对象都会知道自己对应的Class对象,通过Class类对象可以完整地得到其对应的类的信息,通过一系列反射 API。
类class是由 JVM 在执行过程中动态加载的。JVM在第一次读取到一种类class时,会将其加载进内存。
每加载一种class,JVM就为其创建一个Class类的对象,并将两者关联起来。注意:这里的Class类是一个名字叫Class的类class。
以String类为例,当 JVM 加载String类时,它首先读取String.class文件到内存,然后,在堆中为String类创建一个Class类对象并将两者关联起来:
注意:这个Class类对象是 JVM 内部创建的,如果我们查看 JDK 源码,可以发现Class类的构造方法是private,即只有 JVM 能创建Class类对象,我们程序员自己的 Java 程序是无法创建Class类对象的。
由于JVM为每个加载的类class创建了对应的Class类对象,并在实例中保存了该类class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class类对象,我们就可以通过这个Class类对象获取到其对应的类class的所有信息。
这种通过Class实例获取类class信息的方法称为反射(Reflection)。
获取类的私有属性:
这里可能有小伙伴发现了一个问题,如果使用反射可以获取private字段的值,那么类的封装还有什么意义?
答案是一般情况下,我们总是通过p.name来访问Person的name字段,编译器会根据public、protected和private这些访问权限修饰符决定是否允许访问字段,这样就达到了数据封装的目的。
而反射是一种非常规的用法,使用反射,首先代码非常繁琐;其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标对象任何信息的情况下,获取特定字段的值。
封装性存在的意义是隐藏私有内容,为了给使用者提供更方便、更好用的公共接口。但是调用者应该有权限获取对象中私有的内容,因此需要用到反射机制。
Field
Field 提供了有关类或接口的单个属性的信息,以及对它的动态访问的能力。
此外,还有Method、Constructor两个能力:
Method 提供了有关类或接口的单个方法的信息,以及对它的动态访问的能力。
Constructor 提供了有关类的构造方法的信息,以及对它的动态访问的能力。
由于与Field用法相同,这里就不在赘述了。
动态代理
有没有可能不编写实现类,直接在运行期创建某个interface的实例呢?
这是可能的,因为 Java 标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。
什么叫运行期动态创建?
动态代码,我们仍然先定义了接口,但是我们并不去编写实现类,而是直接通过 JDK 提供的一个Proxy.newProxyInstance()方法创建了一个接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代理。JDK 提供的动态创建接口对象的方式,就叫动态代理。