一、什么是反射
Java 反射允许程序在运行时,获取类的完整信息(构造、属性、方法),并且可以操作它们(创建对象、赋值、调用方法),无视 private 修饰符!
正常写代码:必须要知道类名、方法名、而且不能访问private
java
Student s = new Student();
s.name = "张三";
s.study();
反射写代码:不知道具体细节,依旧可以创建对象,调用私有方法、修改私有属性
java
//引号里面是类的位置
Class clazz = Class.forName("com.test.Student");
二、核心作用
- 运行时获取类信息:包名、类名、父类、接口、构造、属性、方法
- 运行时创建对象:哪怕构造器是 private
- 运行时操作属性:修改 / 获取 private、final、static 属性
- 运行时调用方法:调用 private 方法、静态方法
- 框架底层核心技术:Spring、MyBatis、SpringBoot 全靠反射
三、反射的核心API
首先要获取class对象,后续的方法才可以实现:
| 类型 | 核心类 | 作用 |
|---|---|---|
| 类对象 | Class<?> |
代表一个类,反射的入口 |
| 构造器 | Constructor |
操作构造方法,创建对象 |
| 属性 | Field |
操作成员变量(赋值 / 取值) |
| 方法 | Method |
调用普通 / 静态方法 |
| 权限 | setAccessible(true) |
破解 private |
其中获取类对象有三种方式:
类名.class对象.getClass()Class.forName("全类名")
这时候有个疑问:反射是如何获取的呢?
先来了解一下类加载过程:当我们在终端运行javac命令时,会把源代码文件(.java)变成二进制字节码文件(.class),运行java命令后,JVM启动,开始处理.class文件,生成类对象,之后再创建实例对象
在以上过程描述中,分别对应获取class对象的三种方式:
(1)磁盘文件阶段(如图左边)中使用的是:Class.forName("全类名"),这是一种在运行时动态加载类的方式
(2)在编译期间(JVM处理.class文件)使用的是:类名.class,这种方式不需要创建实例对象,直接通过类名获取
(3)运行时阶段(创建了实例对象)使用的是:对象名.getClass(),这个逻辑是当你有了一个实例对象时,可以反向获取他所属类的Class对象
虽然阶段不同,但是无论哪种方式得到的都是同一个Class对象

四、具体使用
使用步骤:1.获取Class对象(对象入口)2.获取要操作的成员(构造/属性/方法)3.开启暴力反射(setAccessible(true))4.执行操作(创建对象 / 赋值 / 调用方法)
在获取的时候,方法分为两类:有Drclared:所有权限都可以获取;无Declared:只获取public权限
1.获取成员变量 Field
getDeclaredFields():获取当前类中所有变量(private /default/protected /public),不包含父类
getFields():只获取public成员变量,包含父类
getDeclaredField("变量名"):获取指定名字的单个变量(任意权限)
getField("变量名"):获取指定名字的public变量
2.获取成员方法 Method
getDeclaredMethods():获取本类所有方法(private /default/protected /public),不包含父类
getMethods():只获取public方法,包含父类
getDeclaredMethod("show", String.class):获取指定方法(任意权限)
getMethod("show", String.class):获取指定的public方法
3.获取构造器 Constructor
getDeclaredConstructors():获取本类所有构造器
getConstructors():获取public构造器
getDeclaredConstructor(String.class):获取指定参数的构造器
getConstructor(参数类型...):获取指定public构造器
4.暴力反射
setAccessible(true):关闭Java 访问检查,可以操作 private 构造、private 变量、private 方法
那什么时候需要暴力反射,什么时候不需要?那就是看private:使用private修饰的,需要暴力反射,非private不需要暴力反射
5.创建对象实例
分为有参和无参
java
//无参
Animal animal1 = (Animal) declaredConstructor.newInstance();
//有参
Animal animal2 = (Animal) del2.newInstance("小猫");
6.操作成员相关方法
注意在获取之前,private修饰的变量,要进行暴力反射
get():获取成员变量值
set():修改成员变量值
invoke(实例对象):调用成员方法
五、反射优缺点
优点:
- 运行时动态获取类信息、动态创建对象
- 解耦,提高程序灵活性
- 框架底层核心技术
缺点:
- 性能比正常代码慢
- 破坏封装性,可以访问私有成员
- 代码复杂,可读性差