Java 反射机制详解 | JavaGuide(Java面试 + 学习指南)
【Java面试】Java反射的优缺点? (qq.com)
面试题系列:什么是Java里的反射 (qq.com)
口语回答
反射是Java语言的重要特性,它能够在程序运行的过程中去构造任意一个类对象,并且可以获取任意一个类的成员变量、成员方法、属性,以及调用任意一个对象的方法。
成员变量与属性区别:
Person类中定义了3个字段 age、name、gender它们是类成员变量,但它们不全是属性;那什么是属性?
属性的定义规则是:set/get方法名,去掉set/get后,将剩余部分首字母小写得到的字符串就是这个类的属性。
通过反射的能力,可以让Java语言支持动态获取程序信息以及动态调用方法的能力。
在Java里面,专门有一个java.lang.reflect用来实现反射相关的类库,包括Construct、Field、Method等类,分别用来获取类的构造方法、成员变量、方法信息。
反射的使用场景还挺多的,比如在动态代理的场景里,使用动态生成的代理类来提升代码的复用性。在Spring框架中,有大量用到反射,比如用反射来实例化Bean对象。
Java反射的优点:
- 增加程序的灵活性。可以在运行的过程中动态对类进行修改和操作
- 提高代码的复用率,比如动态代理就是用到了反射来实现
- 可以在运行时轻松获取任意一个类的方法、属性,并且还能通过反射进行动态调用
Java反射的缺点:
- 反射可能会涉及动态类型的解析,所以JVM无法对这些代码进行优化,导致性能比非反射调用要低
- 使用反射以后,代码的可读性会下降
- 反射可以绕过一些限制访问的属性或者方法,可能会导致破坏了代码本身的抽象性。
获取Class对象的四种方式
1 知道具体类的情况下可以使用
Class alunbarClass = TargetObject.class;
但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化
2 通过Class.forName()传入类的全路径获取
Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");
3 通过对象实例instance.getClass()获取
TargetObject o = new TargetObject();
Class alunbarClass2 = o.getClass();
4 通过类加载器xxxClassLoader.loadClass()传入类路径获取:
ClassLoader.getSystemClassLoader().loadClass("cn.javaguide.TargetObject");
通过类加载器获取Class对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行。
反射的一些基本操作
1 创建一个我们要使用反射操作的类 TargetObject
。
package cn.javaguide;
public class TargetObject {
private String value;
public TargetObject() {
value = "JavaGuide";
}
public void publicMethod(String s) {
System.out.println("I love " + s);
}
private void privateMethod() {
System.out.println("value is " + value);
}
}
2 使用反射操作这个类的方法以及参数
package cn.javaguide;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
/**
* 获取 TargetObject 类的 Class 对象并且创建 TargetObject 类实例
*/
Class<?> targetClass = Class.forName("cn.javaguide.TargetObject");
TargetObject targetObject = (TargetObject) targetClass.newInstance();
/**
* 获取 TargetObject 类中定义的所有方法
*/
Method[] methods = targetClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
/**
* 获取指定方法并调用
*/
Method publicMethod = targetClass.getDeclaredMethod("publicMethod",
String.class);
publicMethod.invoke(targetObject, "JavaGuide");
/**
* 获取指定参数并对参数进行修改
*/
Field field = targetClass.getDeclaredField("value");
//为了对类中的参数进行修改我们取消安全检查
field.setAccessible(true);
field.set(targetObject, "JavaGuide");
/**
* 调用 private 方法
*/
Method privateMethod = targetClass.getDeclaredMethod("privateMethod");
//为了调用private方法我们取消安全检查
privateMethod.setAccessible(true);
privateMethod.invoke(targetObject);
}
}
原理?
通过类加载器将类的字节码文件加载到内存中,并生成对应的Class对象。通过Class对象,可以获取和操作类的各种信息,实现对类的动态调用和操作。
Java反射的运行机制如下:
-
类加载:当Java程序执行时,类加载器根据类名查找并加载对应的类文件,将其字节码文件加载到内存中,并生成对应的Class对象。
-
获取Class对象:通过类加载器加载类后,可以通过Class.forName()方法或对象的getClass()方法获取对应的Class对象。
-
获取类的结构信息:通过Class对象,可以获取类的构造函数、方法和字段等信息。通过Class的getConstructor()、getMethod()和getField()等方法,可以获取指定的构造函数、方法和字段。
-
动态调用:通过Method对象的invoke()方法,可以动态地调用类的方法。通过Field对象的get()和set()方法,可以访问和修改类的字段。
应用场景举例
-
框架和库:许多开源框架和库,如Spring、Hibernate、JUnit等,都使用了反射来实现自动化配置、动态代理、对象关系映射等功能。
-
注解处理器:注解处理器可以通过反射来解析和处理注解,生成相应的代码或实现特定的功能。例如,Java编译器的注解处理器可以通过反射来扫描和处理注解。
-
序列化和反序列化:Java的序列化和反序列化机制可以使用反射来读取和写入对象的属性,实现对象的序列化和反序列化。
-
配置文件的解析和加载:许多应用程序使用配置文件来配置参数和设置,通过反射可以动态地读取和解析配置文件,并根据配置文件的内容加载相应的类和执行相应的操作。
-
动态代理:动态代理是通过反射来实现的,可以在运行时动态地生成代理类,并在代理类中调用被代理对象的方法。
-
测试工具和单元测试:测试工具和单元测试框架可以使用反射来动态地调用被测试类的方法,实现自动化测试和测试结果的收集。
-
动态加载类和资源:通过反射,可以在运行时动态地加载类和资源文件,实现类的动态加载和更新。