文章目录
一、反射的概念
Java的反射机制(Reflection)是Java编程语言的一项重要功能,它允许程序在运行过程中获取类的信息并操作对象或类。反射机制提供了一套获取类的成员变量、方法、构造函数等信息的能力,而不需要在编译时明确知道这些信息。
二、反射的作用和特点
反射机制具有以下作用:
-
运行时获取类的信息:反射机制允许在运行时获取类的信息,包括类的名称、字段、方法、构造函数等。这使得可以在程序运行过程中动态地探索和操作类的结构。
-
动态创建对象:反射机制可以通过类的构造函数动态地创建对象实例,而无需在编译时知道类的具体名称。这对于实现灵活的对象实例化逻辑和依赖注入非常有用。
-
调用对象的方法:反射机制允许在运行时动态地调用对象的方法,包括公有方法和私有方法。这对于实现通用的代码、处理回调函数、实现框架和扩展性强的系统非常有用。
-
操作和修改类的字段:反射机制可以获取和修改类的字段的值,包括私有字段。这对于实现对象关系映射(ORM)框架、持久化对象、序列化和反序列化等功能非常有用。
-
实现注解处理器:反射机制可以用于处理和解析注解。通过反射可以获取类、方法或字段上的注解信息,并根据注解的定义执行相应的逻辑。
特点:
-
动态性:反射机制使得程序在运行时可以动态地获取和操作类的信息,而不需要在编译时确定这些信息。这增加了程序的灵活性和可扩展性。
-
逆向工程:反射机制使得可以对已有的类进行逆向工程,获取其结构和行为的详细信息。这对于框架开发、代码分析和调试非常有用。
-
灵活性和通用性:反射机制可以处理各种类型的对象和类,而不需要针对每个具体类型编写特定的代码。这使得可以编写通用的代码,实现各种功能和行为。
-
安全性和权限检查:反射机制提供了对类、字段和方法的访问权限的检查。通过反射可以绕过访问限制,但是需要适当地设置安全管理器以确保系统的安全性。
-
性能影响:由于反射机制需要进行额外的类信息查询和方法调用,可能会对性能产生一定的影响。因此,在性能敏感的场景下,需要谨慎使用反射机制,并考虑其他更高效的替代方案。
三、与反射相关的类
以下是与反射相关的一些常用的类:
类名 | 用途 |
---|---|
Class | 表示类或接口的元数据 |
Field | 表示类的字段信息 |
Method | 表示类的方法信息 |
Constructor | 表示类的构造函数信息 |
Modifier | 提供对修饰符的操作方法 |
Array | 提供对数组的操作方法 |
Package | 表示包的信息 |
Annotation | 表示注解的信息 |
Parameter | 表示方法或构造函数的参数信息 |
Executable | 表示可执行成员(方法、构造函数)信息 |
这些类都位于java.lang.reflect
包下,通过它们可以实现对类的结构和成员的动态操作和查询。它们为反射机制提供了丰富的功能和灵活性,使得程序可以在运行时获取和操作类的信息。
3.1 Class类
Class
类是反射机制的起源,它是用来表示类和接口的元数据。在运行的Java程序中,每个类在加载到内存中时,都会生成一个对应的Class
示例,该实例包含了类的结构和行为等信息。
Class
类提供了许多方法来获取类的各种信息,例如类的名称、父类、接口、字段、方法、构造函数等。它还提供了创建类的实例、调用类的方法以及访问和修改类的成员的能力。
以下是Class
类的常用方法和的用途:
方法 | 用途 |
---|---|
getName() | 获取类的名称 |
getSuperclass() | 获取类的父类 |
getInterfaces() | 获取类实现的接口 |
getModifiers() | 获取类的修饰符 |
getFields() | 获取类的公有字段 |
getDeclaredFields() | 获取类声明的所有字段,包括私有字段 |
getMethods() | 获取类的公有方法 |
getDeclaredMethods() | 获取类声明的所有方法,包括私有方法 |
getConstructors() | 获取类的公有构造函数 |
getDeclaredConstructors() | 获取类声明的所有构造函数,包括私有构造函数 |
getField(String name) | 获取指定名称的公有字段 |
getDeclaredField(String name) | 获取指定名称的字段,包括私有字段 |
getMethod(String name, Class<?>... parameterTypes) | 获取指定名称和参数类型的公有方法 |
getDeclaredMethod(String name, Class<?>... parameterTypes) | 获取指定名称和参数类型的方法,包括私有方法 |
getConstructor(Class<?>... parameterTypes) | 获取指定参数类型的公有构造函数 |
getDeclaredConstructor(Class<?>... parameterTypes) | 获取指定参数类型的构造函数,包括私有构造函数 |
newInstance() | 使用默认构造函数创建类的一个新实例 |
newInstance(Object... initargs) | 使用指定构造函数和参数创建类的一个新实例 |
isAssignableFrom(Class<?> cls) | 判断当前类是否可以从指定类进行赋值操作 |
isInstance(Object obj) | 判断给定的对象是否是当前类或其子类的实例 |
isInterface() | 判断当前类是否为接口 |
isArray() | 判断当前类是否为数组类型 |
isPrimitive() | 判断当前类是否为基本数据类型 |
isEnum() | 判断当前类是否为枚举类型 |
getAnnotation(Class<A> annotationClass) | 获取指定类型的注解 |
isAnnotationPresent(Class<? extends Annotation> annotationClass) | 判断指定类型的注解是否存在于当前类上 |
这些方法使得可以在运行时动态地探索和操作类的结构,实现动态加载类、反射调用方法、创建对象实例、处理注解等功能。反射机制中的Class
类是反射操作的核心,为实现类的动态性和灵活性提供了重要的支持。
3.2 Constructor类
Constructor
类提供了一系列方法用于获取和操作类的构造函数信息。通过这些方法,可以获取构造函数的名称、参数类型、修饰符、异常类型等。其中,newInstance()
方法可以使用构造函数创建一个新的对象实例,并接受构造函数的参数。setAccessible()
方法用于设置构造函数的可访问性,使得可以访问非公有的构造函数。
以下是Constructor
类的常用方法和的用途:
方法 | 用途 |
---|---|
getDeclaringClass() | 获取定义该构造函数的类对象 |
getName() | 获取构造函数的名称 |
getParameterTypes() | 获取构造函数的参数类型数组 |
getModifiers() | 获取构造函数的修饰符 |
getParameterCount() | 获取构造函数的参数个数 |
getExceptionTypes() | 获取构造函数抛出的异常类型数组 |
newInstance(Object... initargs) | 使用构造函数创建一个新的对象实例 |
setAccessible(boolean flag) | 设置构造函数的可访问性 |
这些方法使得在运行时可以动态地获取和操作类的构造函数,实现对象的动态创建和初始化。反射机制中的Constructor
类为实现对象实例化的灵活性提供了重要的支持。
3.3 Field类
Field
类提供了一系列方法用于获取和操作类的字段信息。通过这些方法,可以获取字段的名称、类型、修饰符等。get()
方法可以获取指定对象上该字段的值,而set()
方法可以设置指定对象上该字段的值。getDeclaringClass()
方法用于获取定义该字段的类对象。
以下是Field
类的常用方法和用途:
方法 | 用途 |
---|---|
getName() | 获取字段的名称 |
getType() | 获取字段的类型 |
getModifiers() | 获取字段的修饰符 |
get(Object obj) | 获取指定对象上该字段的值 |
set(Object obj, Object value) | 设置指定对象上该字段的值 |
getDeclaringClass() | 获取定义该字段的类对象 |
isAccessible() | 判断字段是否可访问 |
setAccessible(boolean flag) | 设置字段的可访问性 |
这些方法使得可以在运行时动态地获取和修改类的字段,实现对对象属性的读取和写入操作。反射机制中的Field
类为实现对象的动态操作提供了重要的支持。
3.4 Method类
Method
类提供了一系列方法用于获取和操作类的方法信息。通过这些方法,可以获取方法的名称、返回类型、参数类型、修饰符等。invoke()
方法可以调用指定对象上的方法,并传递参数进行调用。
以下是Method
类的常用方法和用途:
方法 | 用途 |
---|---|
getName() | 获取方法的名称 |
getReturnType() | 获取方法的返回类型 |
getParameterTypes() | 获取方法的参数类型数组 |
getModifiers() | 获取方法的修饰符 |
invoke(Object obj, Object... args) | 调用指定对象上的方法 |
getDeclaringClass() | 获取定义该方法的类对象 |
isAccessible() | 判断方法是否可访问 |
setAccessible(boolean flag) | 设置方法的可访问性 |
getAnnotation(Class<A> annotationClass) | 获取指定类型的注解 |
isAnnotationPresent(Class<? extends Annotation> annotationClass) | 判断指定类型的注解是否存在于当前方法上 |
getParameterCount() | 获取方法的参数个数 |
getExceptionTypes() | 获取方法抛出的异常类型数组 |
这些方法使得可以在运行时动态地获取和调用类的方法,实现动态执行方法、实现回调功能、实现框架和扩展性强的系统等。反射机制中的Method
类为实现类的动态行为提供了重要的支持。
四、反射案例
4.1 获取Class对象的方法
在反射之前,我们需要做的第一步就是先拿到当前需要反射的类的Class对象,然后通过Class对象的核心方法,达到反射的目的。即:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,然后才能对其进行反射。
在Java中,可以通过以下三种方法获取一个类的Class
对象:
- 使用类的
.class
属性:每个类都有一个.class
属性,通过该属性可以获取对应的Class
对象。
例如,要获取String
类的Class
对象,可以使用String.class
。
Class<String> stringClass = String.class;
- 使用对象的
.getClass()
方法:每个对象都有一个.getClass()
方法,可以返回对象所属类的Class
对象。
例如,要获取字符串对象的类的Class
对象,可以使用str.getClass()
。
String str = "Hello";
Class<? extends String> stringClass = str.getClass();
- 使用
Class.forName()
方法:通过类的全限定名字符串可以使用Class.forName()
方法获取对应的Class
对象。该方法需要提供类的全限定名作为参数,并可能抛出ClassNotFoundException
异常。
例如,要获取java.util.ArrayList
类的Class
对象,可以使用Class.forName("java.util.ArrayList")
。
try {
Class<?> arrayListClass = Class.forName("java.util.ArrayList");
} catch (ClassNotFoundException e) {
// 处理类找不到异常
}
这三种方法都可以获取到一个类的Class
对象,具体使用哪种方法取决于具体的场景和需求。需要注意的是,第三种方法Class.forName()
还可以用于动态加载类,例如在运行时根据配置或条件加载不同的类。
4.2 反射的使用案例
首先准备一个Student
类:
class Student {
//私有属性name
private String name = "张三";
//公有属性age
public int age = 18;
//不带参数的构造方法
public Student() {
System.out.println("Student()");
}
private Student(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Student(String,name)");
}
private void eat() {
System.out.println("i am eating");
}
public void sleep() {
System.out.println("i am sleeping");
}
private void function(String str) {
System.out.println(str);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
这个类中包含了私有属性和共有属性、私有方法和公有方法、私有的公有无参构造以及私有的有参构造。以下是对这个类的反射过程:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectClassDemo {
// 通过反射创建一个对象
public static void reflectNewInstance() {
Class<?> classStudent = null;
try {
classStudent = Class.forName("demo2.Student");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Student student = null;
try {
student = (Student) classStudent.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
System.out.println(student);
}
// 反射私有的构造方法 屏蔽内容为获得公有的构造方法
public static void reflectPrivateConstructor() {
Class<?> classStudent = null;
try {
classStudent = Class.forName("demo2.Student");
// 获取构造方法
Constructor<?> constructor = classStudent.getDeclaredConstructor(String.class, int.class);
// 设置权限
constructor.setAccessible(true);
Student student = (Student) constructor.newInstance("李四", 20);
System.out.println(student);
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException |
InstantiationException e) {
e.printStackTrace();
}
}
// 反射私有属性
public static void reflectPrivateField() {
Class<?> classStudent = null;
try {
classStudent = Class.forName("demo2.Student");
Field field = classStudent.getDeclaredField("name");
Student student = (Student) classStudent.newInstance();
// 设置修改权限
field.setAccessible(true);
field.set(student, "李四");
System.out.println(student);
} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}
// 反射私有方法
public static void reflectPrivateMethod() {
Class<?> classStudent = null;
try {
classStudent = Class.forName("demo2.Student");
Method method1 = classStudent.getDeclaredMethod("function", String.class);
Method method2 = classStudent.getDeclaredMethod("eat");
method1.setAccessible(true);
method2.setAccessible(true);
Student student = (Student) classStudent.newInstance();
System.out.println();
method1.invoke(student, "通过反射获取了私有方法:function");
System.out.println("通过反射获取了私有方法:eat");
method2.invoke(student);
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException |
InvocationTargetException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// reflectNewInstance();
// reflectPrivateConstructor();
// reflectPrivateField();
reflectPrivateMethod();
}
}
五、反射的优点与缺点
反射机制具有以下优点和缺点:
优点:
-
动态性和灵活性:反射机制允许在运行时动态地获取和操作类的信息,使得程序具有更高的灵活性和动态性。可以在运行时加载类、创建对象、调用方法等,适用于动态配置、插件化、框架开发等场景。
-
通用性和可扩展性:反射机制可以对不同的类进行统一的操作,而不需要编写特定的代码。这使得可以编写通用的、适用于各种类的代码,提高了代码的可重用性和可扩展性。
-
元编程能力:反射机制使得程序可以在运行时检查和操作自身的结构和行为,实现元编程的能力。可以动态获取类的信息、注解、属性等,并根据需要进行相应的操作和处理。
-
框架和库的支持:反射机制在许多框架和库中被广泛使用,如ORM框架、依赖注入、单元测试框架等。这些工具和框架利用反射机制实现了强大的功能和扩展性。
缺点:
-
性能开销:反射机制由于需要在运行时进行额外的类信息查询和方法调用,因此比直接调用代码要慢。反射调用方法通常比直接调用方法更耗时,这对于性能敏感的场景需要谨慎使用。
-
安全性问题:反射机制可以绕过访问权限的限制,访问和修改非公有成员。这可能导致代码安全性问题,因此需要谨慎使用反射机制,并在需要时设置适当的安全管理器。
-
编译时检查缺失:反射操作的代码在编译时无法进行静态类型检查,容易导致编译时错误在运行时才暴露出来。这增加了调试和排查问题的难度。
-
可读性和维护性降低:反射代码通常比直接调用代码更加复杂和难以理解。使用反射可以动态地创建和操作对象,但也使得代码变得晦涩,不易于理解和维护。
总而言之,反射机制在一些特定场景下非常有用,但也带来了性能、安全性和可读性等方面的考虑。因此,在使用反射机制时需要谨慎权衡其优缺点,并根据具体情况选择合适的方案。