反射机制
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对象
//通过类的全限定名获取Class对象(编译阶段)
//应用场景:多用于配置文件,读取类全路径,加载类
Class.forName("String className")
//通过类字面常量获取Class对象(加载阶段)
//应用场景:多用于参数传递,比如通过反射得到对应地构造器对象
类.class
//通过对象实例获取Class对象(运行阶段)
//应用场景:通过创建好地对象,获取Class对象
对象.getClass()
- 获取构造函数
//获取指定的公共构造函数
getConstructor(Class<?>... parameterTypes)
//获取指定的构造函数(包括私有构造函数)
getDeclaredConstructor(Class<?> parameterTypes)
//获取所有公共构造函数
getConstructors()
//获取所有构造函数(包括私有构造函数)
getDeclaredConstructors()
- 获取方法
//获取指定的公共方法
getMethod(String name, Class<?>... parameterTypes)
//获取指定的方法(包括私有方法)
getDeclaredMethod(String name, Class<?>... parameterTypes)
//获取所有公共方法
getMethods()
//获取所有方法(包括私有方法)
getDeclaredMethods()
- 获取字段
//获取指定的公共字段
getField(String name)
//获取指定的字段(包括私有字段)
getDeclaredField(String name)
//获取所有公共字段
getFields()
//获取所有字段(包括私有字段)
getDeclaredFields()
- 调用方法
//调用方法
method.invoke(Object obj, Object... args)
- 创建实例
//通过指定的构造函数创建实例
//需要先获得构造方法,然后才能创建实例
Constructor<?> constructor = cls.getConstructor(Class<?>... parameterTypes);
Object instance = constructor.newInstance(Object... initargs);
//法二
Object instance = cls.getConstructor().newInstance();
动态和静态加载
静态加载
定义:静态加载是指在程序启动或初始化时,一次性将所有资源加载到内存中
特点:
- 启动时加载:程序启动时即加载所有资源。
- 内存占用高:所有资源常驻内存,占用较大。
- 响应快:资源已预加载,使用时无需等待。
- 适用场景:适合资源少、内存充足或对响应速度要求高的场景。
优点:
- 使用资源时无需额外加载,响应迅速。
缺点:
- 启动慢,内存占用高,资源利用率低。
动态加载
定义: 动态加载是在程序运行时,按需加载资源。
特点:
- 按需加载:资源在使用时才加载。
- 内存占用低:只加载当前需要的资源,节省内存。
- 响应延迟:首次加载资源时可能有延迟。
- 适用场景:适合资源多、内存有限或资源不常用的场景。
优点:
- 启动快,内存占用低,资源利用率高。
缺点:
- 首次加载资源时可能有延迟,增加运行时复杂性。
动态加载的使用场景:
- 使用
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:通过类名.class
Class<?> clazz = MyClass.class;
// 方式 2:通过对象的 getClass() 方法
MyClass obj = new MyClass();
Class<?> clazz = obj.getClass();
// 方式 3:通过 Class.forName() 动态加载类
Class<?> clazz = Class.forName("com.example.MyClass");
-
获取类的结构信息
- 获取类的基本信息
//获取类名 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);
- 获取类的字段(成员变量)信息
// 获取所有 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); }
- 获取类的方法信息
// 获取所有 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(); }
- 获取类的构造器信息
// 获取所有 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(); }
- 获取类的父类和接口
// 获取父类 Class<?> superClass = clazz.getSuperclass(); // 获取实现的接口 Class<?>[] interfaces = clazz.getInterfaces();
-
反射操作示例
public class Main {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//获取Student类的Class对象
Class<Student> stdCls = Student.class;
//获取Student类的有参构造器
Constructor<Student> declaredConstructor = stdCls.getDeclaredConstructor(String.class, int.class);
//通过有参构造器实例化对象
Student student1 = declaredConstructor.newInstance("xiaomi", 20);
//获取Student类的无参构造器
Constructor<Student> constructor = stdCls.getConstructor();
//通过无参构造器实例化对象
Student student = constructor.newInstance();
//获取Student类的name属性
Field name = stdCls.getField("name");
//设置student对象的name属性值
name.set(student, "xiaoming");
//获取Student类的age属性
Field age = stdCls.getDeclaredField("age");
//由于age是private修饰的,需要设置可访问性
age.setAccessible(true);
//设置student对象的age属性值
age.set(student, 30);
//获取Student类的hi方法并调用
stdCls.getMethod("hi").invoke(student);
//获取Student类的privateMethod方法
Method privateMethod = stdCls.getDeclaredMethod("privateMethod", String.class, int.class);
//由于privateMethod是private修饰的,需要设置可访问性
privateMethod.setAccessible(true);
//调用privateMethod方法
int res = (int)privateMethod.invoke(student, "xiaomi", 30);
}
}
class Student {
public String name;
private int age;
public Student(){}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void hi() {
System.out.println("hi");
}
private int privateMethod(String name, int age) {
++this.age;
return age;
}
}
评论区
请登录后发表评论