反射机制
什么是反射,为什么要使用反射?
首先先看两个定义
-
框架:半成品软件。可以在框架的基础上进行软件开发,简化编码
-
反射:将类的各个组成部分封装为其他对象,这就是反射机制
反射的一个典型应用就是框架设计,在软件开发中,我们希望软件的设计能有更大的适用性。其思想某种程度上与泛型相似,泛型中的思想是:我们不希望某个方法只适用于某个特定类,而只是希望定义一个规范,使得该方法适用于所有会调用该方法的类。而反射的思想是:我们不希望软件的运行依赖某些特定类,而是在软件运行过程中动态地加载所需类,最大程度上提高程序的可扩展性。
⭐️ 反射机制有什么用?
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象(服务器跑起来了不能再重新编译)
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
- 说明:如果想要完成对对象的操作,最重要的就是获取.Class类对象,java中也提供了一些获取Class类对象的API接口
如何使用反射?
在了解反射机制前,首先需要知道的的是java中一个从写到产生一个实例对象之间经历了什么,过程见如下流程图
阶段一:源代码阶段——我们写的.java
代码经过javac编译后会生成.class
文件,这一阶段统称为源代码阶段。
阶段二:Class类对象阶段——在该阶段,java中的类加载器会将第一阶段得到的.class
文件加入进入JVM中,同时解析初其被定义了哪些成员方法,成员变量,构造方法等类的基本属性,将其存储在内存中,以供使用。在上图中,执行完第二阶段后,系统就可以识别Person
对象,但我们输入new Person()
时,系统就不会判断代码为错误。
阶段三:Runtime运行时——当我们在代码中使用new
时,就是将存储在内存中的Class
类实例化的过程。例如上图中我们就通过new Person()
方法得到一个实例化person
对象。
获取目标对象类
上文提到,反射最重要的就是动态的获取目标类对象。因此可以预料得到,在上述的三个阶段中,反射都提供了对应的方法来获取类对象,以Person类为例
-
在Source源代码阶段,(此时内存中没有Person类)
Class.forName("全类名");
:将字节码文件加载进内存,返回class对象(相当于自行把该类加载进入内存,并返回该类)⭐️ 由于这里传入的是字符串,这种方法多用于配置文件。将类名定义在配置文件中,读取文件加载类,该方法最能体现动态性
-
在Class类对象阶段(此时内存中已经有Person类)
类名.class:
通过类名的属性.class属性来获取- 多用于参数的传递
-
在运行阶段(此时已经有了Person的对象实例)
对象.getClass():
getClass()方法在Object类中定义的,java为每一个object类都提供了该方法。- 多用于对象获取字节码的方式。
代码实例如下:
public static void main(String[] args) {
//1.Class.forName("全类名")
Class cls1 = Class.forName("cn.itcast.domain.Person"); //Class类静态方法
System.out.println(cls1);
//2.类名.class
Class cls2 = Person.class; //成员属性
System.out.println(cls2);
//3.对象.getClass()
Person p = new Person();
Class cls3 = p.getClass(); //object的成员方法
System.out.println(cls3);
// 可以用==来比较三个对象,进而判断其是否是同一个变量(==比较地址,equals比较的是字符类型)
System.out.println(cls1==cls2);//true
System.out.println(cls1==cls3);//true
}
⭐️关于Class类的的说明
- 同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个对象**。但不同的实例类,对应的Class类对象都不相同**。
- 类加载过程:程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class)结尾;接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件(.class)加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例(例如这里的Person类)。
- Class实例对应着加载到内存中的一个运行时类(不是类的对象)。
- 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
补充说明:Java中的可以获取Class实例不光是我们自定义的类Person
类,也可以是如下可以是如下类型
- Class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- Interface:接口
- []:数组
- enum:枚举
- annotation:枚举
- primitive type:基本数据类型
- void
构造目标类对象
当获取到目标类后,java提供了相关的API供我们使用来完成希望的功能
构造对象时涉及的相关API
java.lang.Class
:反射的源头java.lang.reflect.Method
:可用于获取目标类的方法java.lang.reflect.Field
:用于获取目标类的成员变量java.lang.reflect.Constructor
:用于获取目标类的构造器
获取类的构造器并构建对象
获取构造方法们;
Constructor<?>[] getConstructors()
:获取所有public的构造方法**(限public修饰)**Constructor<T> getConstructor(Class<?>... parameterTypes)
:获取某个指定参数的构造方法**(限public修饰)**Constructor<?>[] getDeclaredConstructors()
:获取所有构造方法- 用带Declared方法去访问
private
方法时,需要忽略权限修饰符的安全检查——使用暴力反射。
- 用带Declared方法去访问
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
:获取某个指定参数的构造方法
利用构造方法们创建实例化对象
- 使用构造器的
newInstance()
方法构造对象
// 下面的personClass 是person的Class类对象
// Class personClass = Person.class;
Constructor constructor = new personClass.getConstructor(String.class,int.class); //获取参数为(String,int)的构造方法
Constructor[] constructors = new personClass.getConstructor(); //获取所有构造方法,可以通过索引选择
Object person = constructor.newInstance("张三",23);//用该构造方法创建一个新的对象
- 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
Object o = personClass.newInstance();
获取类的成员变量
获取成员变量们;
Field[] getFields()
:获取所有public
修饰的成员变量Field getField(String name)
:获取指定名字的成员变量Field[] getDeclaredFields()
:获取所有的成员变量- 用带Declared方法去访问
private
方法时,需要忽略权限修饰符的安全检查——使用暴力反射。
- 用带Declared方法去访问
Field getDeclaredField(String name)
:获取所指定名字的成员变量
代码实例如下:
Field name = personClass.getDeclaredField("name"); //获取Person类的名为name的变量
获取的变量可以通过对象.set(对象,新属性)
赋值
name.set(person,"zhangsan");// 设置person对象的name属性值为zhangsan
获取并执行类的成员方法
获取成员方法们;
Method[] getMethods()
:获得所有public
修饰的成员方法Method getMethod(String name,Class<?>... parameterTypes)
:获得指定参数内容的public
修饰的成员方法Method[] getDeclaredMethods()
:获得所有成员方法- 用带Declared方法去访问
private
方法时,需要忽略权限修饰符的安全检查——使用暴力反射。
- 用带Declared方法去访问
Method getDeclaredMethod(String name,Class<?>... parameterTypes)
:获得指定参数内容的成员方法
Method eat_method = personClass.getMethod("eat"); //获取Person类的名为 eat 的成员方法
eat_method.invoke(person); //让person对象执行 eat 方法
eat_method2.personClass.getMethod("eat",String.class); // 获取Person类的 名为eat 参数为 String 的成员方法
eat_method2.invoke(p,"饭"); // 让person对象执行该成员方法,并设置参数为"饭"
Methods[] methods = personClass.getMethods(); // 获取Person类所有public修饰的方法
// 注意这里会打印出所有方法,包括其父类的方法