由于在学习框架时,经常会遇到反射,故此篇文章用于对反射的基本学习。
一、概述
基本定义:
对其简单的理解就是将类的各个组成部分封装为其他对象,以便我们能够更加细化的使用。同时,我们也都知道,Java
中程序是运行在虚拟机中,我们平常用文本编辑器或者是IDE编写的程序都是.java
格式的文件,这是最基础的源码,但这类文件是不能直接运行的,必须经过编译成.class
字节码文件进而加载进内存供JVM
虚拟机执行。要想理解反射,就先需要谈起Java
代码在计算机中经历的三个阶段,见下图。
第一个阶段 – 源代码阶段
定义一个了类,继而将一个类进行编译.class
文件。
在第一阶段中,可以通过Class.forName("全类名")
的方式获取Class
类对象。
第二阶段 – 加载进内存阶段(Class对象阶段)
类加载器(ClassLoader
):负责将字节码文件加载进内存。
在内存中需要由一个对象来描述加载进内存中这个Class
文件 – Class
类对象。
Class
类对象的基本组成:
- 成员变量 封装成
Field[] fields
对象 - 构造方法
Constructor[] cons
- 成员方法
Method[] methods
继而通过Class
类对象创建真正的对象,供我们使用。
这一阶段可以通过类名.class
的方式获取到Class
类对象。
第三阶段 – Runtime阶段
通过类的实例化创建出对象。Runtime
运行阶段 new
出类的实例化对象。
通过对象.getClass()
方式获取到Class
类对象。
综上上面的三个阶段对Class
类对象的获取:同一个字节码文件(*.class)
在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class
对象都是同一个(内存中的位置同一个,本质就是一个class
文件)。
好处:
- 可以在程序运行过程中,操作这些对象。(例如:获取一个对象的方法,将一个类进行拆解,对其各个组成部分进行操作,获取到一个类对象在内存中的状态)
- 可以解耦,提高程序的可扩展性(在不改变原有代码的基础上,对功能进行增强实现)。
二、具体使用
Java
反射相关的类:
类名 | 用途 |
---|---|
Class类 | 代表类的实体,在运行的Java应用程序中表示类和接口 |
Field类 | 代表类的成员变量(成员变量也称为类的属性) |
Method类 | 代表类的方法 |
Constructor类 | 代表类的构造方法 |
2.1 Class对象功能
获取成员变量们 | 获取构造方法们 | 获取成员方法们 | 获取全类名 |
---|---|---|---|
Field[] getFields() | Constructor<?>[] getConstructors() | Method[] getMethods() | String getName() |
Field getField(String name) | Constructor |
Method getMethod(String name, 类<?>… parameterTypes) | |
Field[] getDeclaredFields() | Constructor |
Method[] getDeclaredMethods() | |
Field getDeclaredField(String name) | Constructor<?>[] getDeclaredConstructors() | Method getDeclaredMethod(String name, 类<?>… parameterTypes) |
其中*Declared*
代表忽视权限修饰符的安全检查,可以获取一切权限的(成员变量、构造方法,成员方法);而上方的只能获取到public
修饰的(成员变量,构造方法,成员方法)。
在忽视权限修饰符的同时,如果想对其成员变量\构造方法\成员方法使用等,可采用暴力反射(不推荐,降低了安全机制)。
setAccessible(true):暴力反射
– 忽略访问权限修饰符的安全检查
成员变量 –
Field
1.1 设置值
void set(Object obj, Object value)
1.2 获取值
get(Object obj)
构造方法 –
Constructor
2.1 创建对象
T newInstance(Object... initargs)
– 非空参时,获取到Class
类对象的对应的构造方法,再填入对应参数。- 如果使用空参数构造方法创建对象,操作可以简化:
Class
对象的newInstance
方法。例如:Object obj = cls.newInstance
。
方法对象 –
Method
3.1 执行方法
Object invoke(Object obj, Object... args)
3.2 获取方法名称
String getName
:获取方法名
ClassLoader
class类对象.getClassLoader
得到类加载器对象(获取到这个字节码文件对应的类加载器),负责将这个类加载进行内存。ClassLoader
对象可以获取到内存中当前类路径下的文件信息。
三、代码实操
首先定义一个实体类:
Student实体类
1 | public class Student implements Serializable { |
以上的Student
实体类中,包含:
- 成员变量:
name
、age
- 构造方法:
public Student()
public Student(String name, String age)
- 成员方法:
getter
、setter
、public void eat()
、public void eat(String food)
3.1 反射基本方法使用
3.1.1 各阶段对Class
对象的获取
1 | // 1.通过全类名获取class对象 -- 源码阶段 |
其中cls
、cls1
、cls2
为同一个对象,内存地址值相等。
3.1.2 Class对象功能使用 – 成员属性实例
1 | Student student = new Student(); |
3.1.3 Class对象功能使用 – 创建对象
1 | // 有参构造方法 |
3.1.4 Class对象功能使用 – 方法调用
1 | Method eat = cls.getMethod("eat", String.class); // 确定带参的eat()方法 |
3.2 实例
需求:不改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。
定义一个配置文件,配置文件中配置类名、方法名
1
2className=cn.lizhi.domain.Student
methodName=eat代码编写
1
2
3
4
5
6
7
8
9
10
11
12public static void main(String[] args) throws Exception {
Properties pro = new Properties();
// 类加载器 ClassLoader负责将这个类加载进内存
InputStream is = InflectDemo01.class.getClassLoader().getResourceAsStream("pro.properties");
pro.load(is);
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
Class cls = Class.forName(className); // 获取class对象
Object obj = cls.newInstance(); // 创建对象
Method method = cls.getMethod(methodName,String.class); // 加载重载的方法
method.invoke(obj,"fish"); // 执行方法
}当我们需要创建其它类的对象和执行它的方法时,我们只需要修改配置文件即可,方便我们的解耦开发。
参考文献
[1] Java高级特性——反射
[2] 百度百科
[3] 黑马讲义