Java的反射
一、什么是反射
反射是获取类信息的一种能力。反射让 Java 程序可以在运行时动态地加载、访问和修改类、方法、字段等元数据。这种动态能力使得 Java 具备了非常灵活的编程方式,可以在不知道具体实现类的情况下,依然能够操作对象的内部细节。
简而言之,反射就是一种让代码能够“自我了解”的方式。程序运行时能够告诉用户某一个类自己有多少方法、构造器,甚至可以调用自己的方法和修改自己的字段。
二、类信息来自哪里
1、类信息有什么
类的所有元数据和相关信息,这些信息描述了类的结构、行为和属性。通过反射,Java 提供了一个强大的机制,允许开发者在运行时动态获取这些信息。类信息不仅包括类的基本结构(如类名、父类、实现的接口等),还包括类中定义的字段、方法、构造器等详细信息。
1.基本信息
1.1 类名
类的名称,如java.lang.String 或自定义类名。
通过反射获取:clazz.getName()
1.2 父类
类的直接父类,即该类继承的类。每个类都继承自 Object 类(除非显式指定其他父类)。
通过反射获取:clazz.getSuperclass()
1.3 接口
类实现的接口。一个类可以实现多个接口。
通过反射获取:clazz.getInterfaces()
1.4 修饰符
类的访问修饰符(如 public, private, abstract, final 等),以及是否为内部类等信息。
通过反射获取:clazz.getModifiers()
1.5 构造方法
类的构造方法信息,包括构造方法的名称、参数类型、返回类型等。
通过反射获取:clazz.getConstructors(), clazz.getDeclaredConstructors()
2. 字段信息
2.1 字段
类中定义的字段(成员变量)的信息,包括字段名、类型、修饰符等。
通过反射获取:clazz.getFields()(公有字段)、clazz.getDeclaredFields()(所有字段)
2.2 字段修饰符
字段的访问修饰符(如 public, private, protected, static 等)。
通过反射获取:field.getModifiers()
2.3 字段类型
字段的数据类型,可以是基本类型或自定义的类类型。
通过反射获取:field.getType()
2.4 字段值
可以动态地获取或修改对象的字段值,包括私有字段的访问。
通过反射获取:field.get(Object obj), field.set(Object obj, Object value)
3. 方法信息
3.1 方法
类中定义的方法,包括方法名、返回类型、参数类型等。
通过反射获取:clazz.getMethods()(公有方法)、clazz.getDeclaredMethods()(所有方法)
3.2 方法修饰符
方法的访问修饰符(如 public, private, static, final 等)。
通过反射获取:method.getModifiers()
3.3 方法参数类型
方法的参数类型,指明该方法需要的参数。
通过反射获取:method.getParameterTypes()
3.4 方法返回类型
方法的返回类型,即该方法返回的值的类型。
通过反射获取:method.getReturnType()
3.5 方法调用
通过反射可以动态地调用类中的方法,甚至是私有方法。
通过反射调用:method.invoke(Object obj, Object... args)
4. 注解信息
4.1 类注解
类上定义的注解,反射可以用来检查类是否有特定的注解,并获取注解的内容。
通过反射获取:clazz.getAnnotations()
4.2 字段、方法、构造方法的注解
字段、方法和构造方法也可以有注解,反射可以获取它们的注解信息。
通过反射获取:field.getAnnotations(), method.getAnnotations(), constructor.getAnnotations()
5. 内部类信息
5.1 内部类
类内部定义的嵌套类,包括静态内部类和非静态内部类。
通过反射获取:clazz.getDeclaredClasses()
6. 枚举信息
6.1 枚举类型
如果类是枚举类型,反射可以获取枚举的成员。
通过反射获取:clazz.getEnumConstants()
7. 数组信息
7.1 数组类型
如果类是数组类型,反射可以获取数组的元素类型。
通过反射获取:clazz.getComponentType()
8. 类加载器信息
8.1 类加载器
类加载器负责将类的字节码加载到 JVM 中。通过反射可以获取类的类加载器。
通过反射获取:clazz.getClassLoader()
9. 类的安全信息
9.1 安全性检查
Java 的 SecurityManager 可以用来限制反射的某些操作,尤其是在沙箱环境中。
通过反射获取:clazz.isAnnotation(), clazz.isInterface(), clazz.isEnum()
2、类信息来自哪里
1.Java程序运行的三个阶段
1.1源代码编译阶段(磁盘)
在这个阶段,Java 源代码(通常是 .java 文件)将被 Java 编译器(javac)编译成字节码文件(.class 文件)。这个字节码是平台无关的,可以在任何支持 Java 的操作系统上运行。
源代码文件:开发者用 .java 后缀编写的代码文件。
编译过程:javac 编译器将 .java 文件编译成 .class 文件,.class 文件中存储的是 JVM(Java Virtual Machine)能够理解的字节码。
javac HelloWorld.java # 生成 HelloWorld.class 字节码文件
1.2 类加载阶段(Class类对象阶段)
Java 程序的类加载过程由 JVM 完成。JVM 会通过类加载器(ClassLoader)来加载字节码文件(.class 文件)。这个阶段可以分为几个子步骤:
1.2.1 加载
当 JVM 启动时,它通过类加载器加载所需的类文件。在类加载过程中,JVM 会将 .class 文件读取到内存中,并生成一个对应的 Class 对象,这个对象包含类的元数据。加载的方式主要是以下几种:
引导类加载器:负责加载 JDK 核心类库(如 java.lang 包下的类)。
扩展类加载器:加载 JDK 扩展库中的类(如 lib/ext 目录下的类)。
系统类加载器:负责加载用户类路径(classpath)中的类。
1.2.2 链接
链接过程会对加载的类进行验证、准备和解析:
验证:验证字节码文件的合法性,确保它符合 JVM 的要求,防止非法代码的执行。
准备:为类的静态变量分配内存,并赋初始值。
解析:将常量池中的符号引用替换为直接引用。
1.2.3 初始化
在类被加载并链接后,JVM 会执行类的初始化操作。初始化的主要任务是执行静态初始化器(如 static 块或静态变量的初始化)。这一步保证了类的静态内容被正确初始化。
1.3 运行时阶段
经过前面的编译和加载,Java 程序就准备好运行了。此时,JVM 将开始执行类中的 main 方法或其他指定的入口方法,进入程序的实际执行阶段。
1.3.1 解释执行与即时编译
JVM 通过解释执行或者即时编译来执行字节码:
解释执行:JVM 解释器按行解释字节码并执行。
即时编译:JIT 编译器将字节码转换为本地机器代码并直接执行,这可以显著提高性能,因为编译后代码的执行速度比解释执行要快。
1.3.2 垃圾回收
在 Java 程序的执行过程中,JVM 会负责垃圾回收(GC)。JVM 定期回收不再使用的对象和内存,以保证程序的内存使用效率。
1.3.3 线程调度与执行
如果程序使用了多线程,JVM 还会调度和管理线程的执行。多线程的并发执行与同步也是 JVM 执行的关键部分。
2.类信息的来源
在 Java 中,每个类在加载时,JVM 会为其生成一个 Class 对象。这个Class 对象包含了该类的元数据,包括类名、字段、方法、构造器等信息。每个 Class 对象都是唯一的。
通过类名直接获取: Class.forName("类名")
通过对象获取:类对象.getClass()
通过 .class 获取: 类名.class
只有获取了类对象才能获取类信息
三、如何获取类信息
以Student类为例:
package 反射;
public class Student {
// 属性
private String name;
public int age;
double height;
protected char sex;
// 方法
private void run() {
System.out.println("学生跑的老快");
}
public int getAge() {
return this.age;
}
public String getNameString(String name) {
return this.name = name;
}
protected void run(String a, int b) {
System.out.println(a + " " + b);
}
// 构造方法
Student() {
}
Student(double height, char sex) {
this.height = height;
this.sex = sex;
}
Student(String name, int age, double height, char sex) {
this.name = name;
this.age = age;
this.height = height;
this.sex = sex;
}
public Student(int age, double height, char sex) {
this.age = age;
this.height = height;
this.sex = sex;
}
private Student(String name, double height, char sex) {
this.name = name;
this.height = height;
this.sex = sex;
}
protected Student(String name, int age, char sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
private Student(char sex) {
this.sex = sex;
}
}
1、获取Class对象
Class clazz = Class.forName("反射.Student");
// Class clazz = Student.class;
// Student stu1 = new Student();
// Class clazz = stu1.getClass();
2、获取构造方法
获取所有构造方法的信息
// 获取构造器
Constructor[] constructors = clazz.getDeclaredConstructors();
System.out.println(Arrays.toString(constructors));
根据不同构造方法中的参数类型进行对应构造方法的获取
Constructor constructor1 = clazz.getDeclaredConstructor(String.class,int.class,double.class,char.class);
System.out.println(constructor1);
Constructor constructor2 = clazz.getDeclaredConstructor(int.class,double.class,char.class);
System.out.println(constructor2);
Constructor constructor3 = clazz.getDeclaredConstructor(String.class,double.class,char.class);
System.out.println(constructor3);
Constructor constructor4 = clazz.getDeclaredConstructor(String.class,int.class,char.class);
System.out.println(constructor4);
3、获取字段
加上declare能获取所有,不加只能获取public修饰的内容
Field[] fields = clazz.getDeclaredFields();
System.out.println(Arrays.toString(fields));
Field[] fields1 = clazz.getFields();
System.out.println(Arrays.toString(fields1));
获取指定名字的字段
Field nameField = clazz.getDeclaredField("name");
System.out.println(nameField);
4、获取方法
获取所有普通方法的信息
// 获取方法
Method[] methods= clazz.getDeclaredMethods();
System.out.println(Arrays.toString(methods));
Method[] methods1= clazz.getMethods();
System.out.println(Arrays.toString(methods1));
获取指定方法
Method runMethod = clazz.getDeclaredMethod("run");
Method runMethod1 = clazz.getDeclaredMethod("run",String.class,int.class);
System.out.println(runMethod);
System.out.println(runMethod1);
Method getAge = clazz.getDeclaredMethod("getAge");
System.out.println(getAge);
Method getNameString = clazz.getDeclaredMethod("getNameString",String.class);
System.out.println(getNameString);
四、如何使用类信息
1、暴力反射
private 修饰的数据是在其他类中访问不到的,所以只能忽略访问权限修饰符的安全检查。
暴力反射针对的就是private修饰的数据。
Class clazz = Class.forName("反射.Student");
Constructor constructor = clazz.getDeclaredConstructor();
Student student =(Student) constructor.newInstance();
Constructor constructor1 = clazz.getDeclaredConstructor(double.class,char.class);
Student student1 = (Student) constructor1.newInstance(185.5,'男');
Constructor constructor2 = clazz.getDeclaredConstructor(char.class);
// 暴力反射
constructor2.setAccessible(true);
Student student2 = (Student) constructor2.newInstance('女');
2、对属性赋值取值
set() 方法 需要两个参数,分别是对象和当前要赋的值
Field nameField = clazz.getDeclaredField("name");
// private 修饰的name需要暴力反射
nameField.setAccessible(true);
nameField.set(student, "张三");
System.out.println(nameField.get(student));
Field ageField = clazz.getField("age");
ageField.setAccessible(true);
ageField.set(student, 10);
System.out.println(ageField.get(student));
Field heightField = clazz.getDeclaredField("height");
heightField.set(student, 158.3);
System.out.println(heightField.get(student));
Field sexField = clazz.getDeclaredField("sex");
sexField.set(student, '女');
System.out.println(sexField.get(student));
3、动态调用方法
在程序运行时动态地决定调用哪个方法,甚至可以通过参数传递动态执行的行为
Method run = clazz.getDeclaredMethod("run");
run.setAccessible(true);
run.invoke(student);
Method getAge = clazz.getDeclaredMethod("getAge");
getAge.invoke(student);
Method getStringName = clazz.getDeclaredMethod("getNameString", String.class);
String name= (String) getStringName.invoke(student, "老铁");
System.out.println(name);
Method run1 = clazz.getDeclaredMethod("run", String.class,int.class);
run1.invoke(student, "666",99);
五、反射的特点
1、反射的优势
优势 | 说明 |
动态性 | 在运行时动态加载和调用方法与字段。 |
灵活性 | 可以实现如依赖注入、工厂模式等设计模式 |
支持多框架 | Spring、Hibernate 等框架广泛使用反射 |
可访问私有成员 | 反射可以让你访问甚至修改类的私有字段和方法 |
2、反射的不足
缺点 | 说明 |
性能开销 | 反射机制的调用相对较慢,特别是在频繁使用时 |
类型安全问题 | 使用反射时,编译时无法检查类型,可能导致运行时错误 |
代码复杂性 | 过度使用反射可能导致代码变得难以维护和调试 |
3、反射中常见的类
类 | 说明 |
Class 类 | 代表类的元数据,获取类信息、创建实例等 |
Method 类 | 代表类中的方法,提供方法信息,动态调用 |
Field 类 | 代表类中的字段,提供字段信息和动态修改字段的能力 |
Constructor 类 | 代表类的构造方法,用于动态创建对象 |
Array 类 | 处理数组的反射,提供数组创建、操作方法 |
4、反射的性能开销
与直接调用方法或访问字段相比,反射的速度要慢很多。这是因为反射涉及到大量的元数据查找和动态代理操作