Java反射
反射机制
Java反射机制是Java提供的一种在运行时动态获取类信息并操作类或对象的机制。通过反射,可以在程序运行时获取类的构造方法,成员变量,方法等信息,并调用他们。
反射的优点与缺点
- 优点:可以动态地创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑
- 缺点:使用反射基本是解释执行,对执行速度有影响
反射的核心类
- Class类:表示类的元数据,表示某个类加载后在堆中的对象,是反射的入口
- Constructor类:表示类的构造方法
- Field类:表示类的成员变量
- Method类:表示类的方法
底层运作
反射的主要功能
- 获取类的信息:
- 获取类的名称,修饰符,父类,接口,注解等
- 获取类的构造方法,成员变量,方法等信息
- 动态创建对象:
- 通过
Class.newInstance()
或Constructor.newInstance()
动态创建对象
- 通过
- 动态调用方法:
- 通过
Method.invoke()
动态调用对象的方法。
- 通过
- 动态访问和修改字段:
- 通过
Field.get()
和Field.set()
访问和修改对象的字段。
- 通过
- 操作数组:
- 通过
Array
类动态创建和操作数组。
- 通过
反射相关类
- java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
- java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
- java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
- java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示某个类的构造方法
常用方法
- 获取Class对象
1 | //通过类的全限定名获取Class对象(编译阶段) |
- 获取构造函数
1 | //获取指定的公共构造函数 |
- 获取方法
1 | //获取指定的公共方法 |
- 获取字段
1 | //获取指定的公共字段 |
- 调用方法
1 | //调用方法 |
- 创建实例
1 | //通过指定的构造函数创建实例 |
动态和静态加载
静态加载
定义:静态加载是指在程序启动或初始化时,一次性将所有资源加载到内存中
特点:
- 启动时加载:程序启动时即加载所有资源。
- 内存占用高:所有资源常驻内存,占用较大。
- 响应快:资源已预加载,使用时无需等待。
- 适用场景:适合资源少、内存充足或对响应速度要求高的场景。
优点:
- 使用资源时无需额外加载,响应迅速。
缺点:
- 启动慢,内存占用高,资源利用率低。
动态加载
定义:
动态加载是在程序运行时,按需加载资源。
特点:
- 按需加载:资源在使用时才加载。
- 内存占用低:只加载当前需要的资源,节省内存。
- 响应延迟:首次加载资源时可能有延迟。
- 适用场景:适合资源多、内存有限或资源不常用的场景。
优点:
- 启动快,内存占用低,资源利用率高。
缺点:
- 首次加载资源时可能有延迟,增加运行时复杂性。
动态加载的使用场景:
- 使用
Class.forName()
动态加载类,在运行时根据类名加载类,并返回对应的class对象
类加载
类加载是 JVM将类的字节码文件(.class
文件)加载到内存中,并转换为 Class
对象的过程。
类加载的过程
类加载的过程可以分为以下几个阶段:
- 加载(Loading)
- 通过类的全限定名(包名 + 类名)查找字节码文件。
- 将字节码文件加载到内存中,并生成一个
java.lang.Class
对象。 - 加载可以由 JVM 自带的类加载器完成,也可以由用户自定义的类加载器完成。
- 验证(Verification)
- 确保加载的字节码文件符合 JVM 规范,防止恶意代码破坏 JVM。
- 验证内容包括文件格式、元数据、字节码和符号引用等。
- 准备(Preparation)
- 为类的静态变量分配内存,并设置默认初始值(如
0
、null
等)。 - 如果是常量(
final static
),则直接赋值为代码中定义的值。
- 为类的静态变量分配内存,并设置默认初始值(如
- 解析(Resolution)
- 将常量池中的符号引用(Symbolic Reference)转换为直接引用(Direct Reference)。
- 符号引用是类、方法、字段的名称,直接引用是具体的内存地址。
- 初始化(Initialization)
- 执行类的静态代码块(
static {}
)和静态变量的赋值操作。 - 这是类加载的最后一步,JVM 会保证类的初始化在多线程环境下是线程安全的。
- 执行类的静态代码块(
获取类结构信息
- 获取Class对象
1 | // 方式 1:通过类名.class |
获取类的结构信息
- 获取类的基本信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//获取类名
String className = clazz.getName();//全限定名(包名+类名)
String simpleName = clazz.getSimpleName();//简单类名
//获取包名
Package pkg = clazz.getPackage();
/**获取类的修饰符(public,final,absetract等)
/*public : 1, private : 2, protected : 4, static : 8, final : 16
/*synchronized : 32, volatile(表示字段是易变的): 64, transient(表示字段不会被序列化): 128, native(表示字段是本地方法): 256
/*interface : 512, abstract : 1024, strictfp(表示类或者方法使用严格的浮点计算): 2048
**/
int modifiers = clazz.getModifiers();
//将修饰符对应的二进制再转成对应的修饰符
String modifierStr = Modifier.toString(modifiers);- 获取类的字段(成员变量)信息
1
2
3
4
5
6
7
8
9
10
11// 获取所有 public 字段(包括父类的 public 字段)
Field[] publicFields = clazz.getFields();
// 获取所有字段(包括私有字段,但不包括父类的字段)
Field[] allFields = clazz.getDeclaredFields();
for (Field field : allFields) {
String fieldName = field.getName(); // 字段名
Class<?> fieldType = field.getType(); // 字段类型
int fieldModifiers = field.getModifiers(); // 字段修饰符
String modifierStr = Modifier.toString(fieldModifiers);
}- 获取类的方法信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 获取所有 public 方法(包括父类的 public 方法)
Method[] publicMethods = clazz.getMethods();
// 获取所有方法(包括私有方法,但不包括父类的方法)
Method[] allMethods = clazz.getDeclaredMethods();
for (Method method : allMethods) {
String methodName = method.getName(); // 方法名
Class<?> returnType = method.getReturnType(); // 返回值类型
int methodModifiers = method.getModifiers(); // 方法修饰符
String modifierStr = Modifier.toString(methodModifiers);
// 获取方法的参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
}- 获取类的构造器信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 获取所有 public 构造器
Constructor<?>[] publicConstructors = clazz.getConstructors();
// 获取所有构造器(包括私有构造器)
Constructor<?>[] allConstructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : allConstructors) {
String constructorName = constructor.getName(); // 构造器名
int constructorModifiers = constructor.getModifiers(); // 构造器修饰符
String modifierStr = Modifier.toString(constructorModifiers);
// 获取构造器的参数类型
Class<?>[] parameterTypes = constructor.getParameterTypes();
}- 获取类的父类和接口
1
2
3
4
5// 获取父类
Class<?> superClass = clazz.getSuperclass();
// 获取实现的接口
Class<?>[] interfaces = clazz.getInterfaces();反射操作示例
1 | public class Main { |
暴破
暴破指的是通过反射或其他技术手段绕过访问控制(如访问私有字段或方法),或者通过修改类的字节码来实现一些非常规操作。
- 反射暴破
通过反射可以访问和修改类的私有成员(字段、方法、构造器等),如果不使用暴破只能访问私有变量,而不能修改私有变量。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Czar!
评论
ValineDisqus