一、反射是什么?
反射是Java语言的一个特性,它允许程序再运行时,进行自我检查并且对内部的成员进行操作。
反射是指在运行时检查、获取和操作类、方法、字段等程序元素的能力。简而言之,它让我们能够检查和修改代码的结构,而不仅仅是执行代码。反射使得Java程序能够在运行时了解自身的结构,并动态地创建、操作和销毁对象,以及调用对象的方法。
反射就是在程序运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
二、为什么要用反射?
Java Reflection功能非常强大,并且非常有用,比如:
- 获取任意类的名称、package信息、所有属性、方法、注解、类型、类加载器等
- 获取任意对象的属性,并且能改变对象的属性
- 调用任意对象的方法
- 判断任意一个对象所属的类
- 实例化任意一个类的对象
- 通过反射我们可以实现动态装配,降低代码的耦合度,动态代理等。
三、反射相关API
Java中的反射功能主要通过下面的类和接口实现:
- Class :用于获取类的信息
- Field:用于获取和设置类的字段
- Method:用于获取类的方法
- Constructor:用于获取类的构造函数
- Array:用于操作数组
- Modifier:用于获取字段,方法和类的修饰符。
- …
四、反射的使用
Class类常用方法
- 获取类的方法
方法名称 | 功能描述 |
---|---|
public Method[] getMethods() | 获取该类的所有公有的方法(public 修饰的) |
public Method getMethod(String name, Class<?>… parameterTypes) | 获取该类的某个公有方法(public 修饰的) |
public Method[] getDeclaredMethods() | 获取该类的所有方法 |
public Method getDeclaredMethod(String name, Class<?>… parameterTypes) | 获取该类的某个方法 |
- 获取构造器
方法名称 | 功能描述 |
---|---|
public Constructor getConstructor(Class<?>… parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
public Constructor<?>[] getConstructors() | 获得该类的所有公有构造方法 |
public Constructor getDeclaredConstructor(Class<?>… parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
public Constructor<?>[] getDeclaredConstructors() | 获得该类的所有构造方法 |
- 获取字段
方法名称 | 功能描述 |
---|---|
public Field getField(String name) | 获得该类中名为参数相同的公共字段 |
public Field[] getFields() | 获得该类中所有的公共字段 |
public Field getDeclaredField(String name) | 获取类中字段名和参数相同的字段 |
public Field[] getDeclaredFields() | 获取类中所有的字段属性 |
- Class Field Method Constructor
Class类 | 代表类的实体,在允许的Java应用程序中表示类和接口 |
---|---|
Field类 | 代表类的成员变量(类的属性) |
Method类 | 代表类的方法 |
Constructor类 | 代表类的构造器 |
提示:在反射中,方法名命名都是大差不差的,而且归根结底也就是获取和类相关的东西,比如:字段属性,方法,构造器,泛型,注解,实现的接口,继承的父类…本文仅仅展示了一些 Constructor Method Field的冰山一角
常见操作
在Java中,每个类都有一个关联的 Class 对象,该对象包含了有关该类的信息。Class 类提供了许多方法,可以用来获取关于类的信息,例如类的名称、超类、实现的接口、构造函数、字段和方法等。
获取Class对象
- 使用Class.forName()方法获取
Class<?> clazz1 = Class.forName("com.robin._reflect.Student");
- 使用.class获取
Class<?> clazz2 = Student.class;
- 使用对象的.getClass()获取
Class<?> clazz3 = new Student().getClass();
获取类的信息
// 通过Class类对象获取类的信息
String className = clazz1.getName();
System.out.println("类名:"+className);
获取超类的信息
Class<?> superclass = clazz1.getSuperclass();
System.out.println("超类:"+superclass.getName()); // 打印超类名称
获取实现的接口
Class<?>[] interfaces = clazz1.getInterfaces();
for (Class<?> iface : interfaces) {
System.out.println("实现的接口:" + iface.getName());
}
创建对象
- 通过Class对象的newInstance()创建对象
Object obj = clazz1.newInstance();
System.out.println("obj:"+obj.toString());
- 通过Constructor对象的newInstance()创建对象
Constructor<?> constructor = clazz1.getConstructor(null); // 传入构造器需要的参数
Object newInstance = constructor.newInstance(args);// 使用构造函数创建对象
获取和设置字段的值
Field field = clazz1.getDeclaredField("name");
field.setAccessible(true); // 设置允许访问私有字段
Object fieldValue = field.get(obj); // 获取字段的值
System.out.println("字段值:"+fieldValue);
field.set(obj,"zhangsan");
Object newFieldValue = field.get(obj); // 获取字段新设的值
System.out.println("字段值:"+newFieldValue);
调用方法
Method toStringMethod = clazz1.getMethod("toString", null);
Object result = toStringMethod.invoke(obj, args);// 调用执行方法
System.out.println("方法结果:"+result);
获取和使用构造函数
Constructor<?> constructor = clazz1.getConstructor(null);
Object newInstance = constructor.newInstance(args);// 使用构造函数创建对象
System.out.println("新对象创建了:"+newInstance.toString());
练习操作
首先准备一个用于被反射的类,以Student为例
public class Student {
private String name; // 私有成员变量
private String phone;// 私有成员变量
public String sex; // 公有成员变量
public String address;// 公有成员变量
// 公有构造器
public Student() {
}
// 私有构造器
private Student(String name, String phone) {
this.name = name;
this.phone = phone;
}
// 保护构造器
protected Student(String name){
this.name = name;
}
// 公有的getter setter 方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
// 公有的toString 方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
练习的代码:由于步骤较多我就不一一摘出来说明了,统一放到代码注释中说明
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Demo2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchFieldException {
// 1.获取Class类对象
Class<?> clazz = Class.forName("com.robin._reflect.Student");
// 2. 获取构造器
// 2.1 获取public的构造器
Constructor<?>[] pbConsturctors = clazz.getConstructors();
for (Constructor<?> pbConsturctor : pbConsturctors) {
System.out.println(pbConsturctor.getName()+" 访问控制权限:"+pbConsturctor.getModifiers());
}
System.out.println("=========================");
// 2.2 获取所有的构造器
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor.getName()+" 访问控制权限:"+declaredConstructor.getModifiers());
}
// 3.创建对象
Object obj1 = clazz.newInstance();
Constructor<?> constructor = clazz.getConstructor(null); // 获取空参的构造器对象
Object obj2 = constructor.newInstance();
Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(String.class);
Object obj3 = declaredConstructor.newInstance("张三");
// 4.获取方法
Method toStringMethod = clazz.getMethod("toString");
// 执行方法 invoke(对象)
Object invokeMethod1 = toStringMethod.invoke(obj1);
System.out.println(invokeMethod1.toString());
Object invokeMethod2 = toStringMethod.invoke(obj2);
System.out.println(invokeMethod2.toString());
Object invokeMethod3 = toStringMethod.invoke(obj3);
System.out.println(invokeMethod3.toString());
System.out.println("==========================");
// 5.获取字段属性
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField.getName());
}
Field field = clazz.getDeclaredField("phone");
// 更改非public属性需要提供权限
field.setAccessible(true);
// 5.1 获取字段属性值
Object fieldValue = field.get(obj1);// null
System.out.println(fieldValue==null? "null" : fieldValue.toString());
// 5.2 更改属性值
field.set(obj1,"15547182477");
Object newFieldValue = field.get(obj1);
System.out.println(newFieldValue.toString());// 15547182477
}
}
五、反射的注意事项
- 安全性:反射可以绕过访问控制,因此在使用反射时要格外小心,确保只访问允许的成员和方法。如果不小心访问了私有成员或调用了不安全的方法,可能会导致应用程序不稳定或不安全。
- 性能:反射操作通常比直接调用方法或访问字段的方式要慢。因此,在性能敏感的应用程序中,要谨慎使用反射,尽量选择其他更高效的方法。
- 编译时检查:反射可以绕过编译时类型检查,因此如果在使用反射时传递了错误的类型或方法名称,可能会导致运行时异常。要特别小心避免这种情况。
- 类加载:反射可能会触发类的加载,这可能会导致不希望加载的类被加载到内存中。要注意控制类加载的时机。
总之,反射是一项强大的功能,但需要小心谨慎地使用。只有在必要的情况下才应该使用反射,确保安全性和性能。在日常开发中,应优先考虑使用普通的方法调用和字段访问,只有在没有其他选择时才考虑使用反射。
六、反射的应用场景
反射在 Java 中有广泛的应用场景,以下是几个常见的应用场景:
① 动态加载类和创建对象
通过反射,我们可以在运行时动态地加载类,并创建其实例。这样就可以根据配置文件或用户输入来决定要加载的类,从而实现灵活的代码扩展性。
String className = "com.example.MyClass";
Class<?> clazz = Class.forName(className);
Object obj = clazz.newInstance();
② 调用对象的方法
通过反射,我们可以在运行时动态地调用对象的方法。这样就可以根据不同的条件来选择调用不同的方法,实现更加灵活的业务逻辑。
Method method = obj.getClass().getMethod("methodName", parameterTypes);
method.invoke(obj, args);
③ 操作对象的属性
通过反射,我们可以在运行时动态地操作对象的属性。这样就可以读取或修改对象的私有字段,实现对对象状态的灵活控制。
Field field = obj.getClass().getDeclaredField("fieldName");
field.setAccessible(true);
Object value = field.get(obj);
field.set(obj, newValue);
④ 获取类的信息
通过反射,我们可以在运行时动态地获取类的信息。这样就可以获取类的构造方法、字段、方法等信息,并进行相应的操作。
Class<?> clazz = obj.getClass();
Constructor<?>[] constructors = clazz.getConstructors();
Field[] fields = clazz.getDeclaredFields();
Method[] methods = clazz.getDeclaredMethods();
⑤ 注解处理器
通过反射,我们可以编写注解处理器来处理自定义注解。这样就可以在编译期间或运行时对注解进行解析和处理,实现一些特定的功能。
Class<?> clazz = MyClass.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof MyAnnotation) {
// 处理自定义注解逻辑
}
}
七、反射的优缺点
反射的优点
- 动态性:通过反射,我们可以在运行时动态地加载类、创建对象、调用方法和操作属性,使得代码更加灵活和可扩展。
- 配置性:通过反射,我们可以根据配置文件或用户输入来决定要加载的类、调用的方法和操作的属性,实现了代码的配置化。
- 框架支持:许多 Java 框架(如 Spring)都广泛使用了反射机制,通过反射来实现依赖注入、AOP 等功能。
反射的缺点
- 性能开销:反射操作相比直接调用方法和访问属性,会有一定的性能开销。因此,在性能要求较高的场景下,应慎重使用反射。
- 安全性问题:通过反射可以绕过 Java 语言的访问控制机制,访问私有字段和方法。这可能导致代码的安全性问题,需要谨慎使用