Java 进阶:反射机制深度解析
Java-day19
一、反射机制概述:什么是 Java 反射?
1.1 反射的定义
Java 反射是指程序在**运行时**可以**获取类的所有信息**(包括接口、父类、构造器、方法、变量等),并可以动态操作类的成员(如创建对象、调用方法、修改变量)的能力。
简单来说,反射让 Java 程序具备了“**自我检查**”和“**动态操作**”的特性,是 Java 实现“元编程”的核心机制。
1.2 反射的应用场景
- 框架开发(如 Spring、MyBatis):通过反射实现组件的自动装配、SQL 语句与方法的映射等;
- 动态代理:在运行时生成代理类,实现 AOP(面向切面编程);
- 类信息检查:在工具类中获取类的结构信息(如 IDE 的“查看类结构”功能)。
二、反射的原理:类加载与 Class 对象
2.1 类的加载过程
Java 类的生命周期分为三个阶段:
- **磁盘文件阶段**:类以 .java 源文件或 .class 字节码文件的形式存储在磁盘;
- **类对象阶段**:编译器将 .java 编译为 .class 后,JVM 加载 .class 文件,将类的信息(接口、父类、构造器、方法、变量等)封装到一个 Class 对象中,并存储在方法区;
- **运行时阶段**:通过 Class 对象创建实例,执行对象的方法。
**核心**:类的信息(如成员变量、方法)主要存储在**类对象阶段**的 Class 对象中,反射的所有操作都基于这个 Class 对象。
2.2 Class 对象的获取方式
每个类在 JVM 中都有且仅有一个 Class 对象,获取 Class 对象有三种方式:
(1)通过 Class.forName("全类名") 获取(最常用,支持动态加载)
```java
try {Class clazz = Class.forName("com.qcby.Animal");} catch (ClassNotFoundException e) {e.printStackTrace();}```
(2)通过 类名.class 获取(编译期确定类,静态方式)
```java
Class clazz = Animal.class;```
(3)通过 对象.getClass() 获取(基于已有对象)
```java
Animal animal = new Animal();Class clazz = animal.getClass();```
**验证:三种方式获取的是同一个 Class 对象**
```java
public class Test {public static void main(String[] args) throws ClassNotFoundException {// 方式1Class clazz1 = Class.forName("com.qcby.Animal");// 方式2Class clazz2 = Animal.class;// 方式3Animal animal = new Animal();Class clazz3 = animal.getClass();// 比较是否为同一个对象System.out.println(clazz1 == clazz2); // trueSystem.out.println(clazz2 == clazz3); // true}}```
**结论**:一个类在一次程序运行过程中只会被加载一次,因此通过任何方式获取的 Class 对象都是同一个。
三、反射实战:获取类的信息
3.1 获取类的成员变量(Field)
通过 Class 对象可以获取类的所有变量(包括公共、私有、静态变量等)。
(1)获取所有声明的变量(含私有、保护):getDeclaredFields()
```java
import java.lang.reflect.Field;import java.util.Arrays;public class FieldDemo {public static void main(String[] args) throws ClassNotFoundException {Class clazz = Class.forName("com.qcby.Animal");Field[] declaredFields = clazz.getDeclaredFields();System.out.println("所有声明的变量:" + Arrays.toString(declaredFields));}}```
(2)获取公共变量(仅 public 修饰):getFields()
```java
Field[] fields = clazz.getFields();System.out.println("公共变量:" + Arrays.toString(fields));```
3.2 获取类的方法(Method)
通过 Class 对象可以获取类的所有方法(包括公共、私有、静态方法等)。
(1)获取所有声明的方法(含私有、保护):getDeclaredMethods()
```java
import java.lang.reflect.Method;import java.util.Arrays;public class MethodDemo {public static void main(String[] args) throws ClassNotFoundException {Class clazz = Class.forName("com.qcby.Animal");Method[] declaredMethods = clazz.getDeclaredMethods();System.out.println("所有声明的方法:" + Arrays.toString(declaredMethods));}}```
(2)获取公共方法(仅 public 修饰,包括继承的方法):getMethods()
```java
Method[] methods = clazz.getMethods();System.out.println("公共方法:" + Arrays.toString(methods));```
3.3 获取类的构造器(Constructor)
通过 Class 对象可以获取类的所有构造器。
(1)获取所有声明的构造器(含私有、保护):getDeclaredConstructors()
```java
import java.lang.reflect.Constructor;import java.util.Arrays;public class ConstructorDemo {public static void main(String[] args) throws ClassNotFoundException {Class clazz = Class.forName("com.qcby.Animal");Constructor[] declaredConstructors = clazz.getDeclaredConstructors();System.out.println("所有声明的构造器:" + Arrays.toString(declaredConstructors));}}```
(2)获取公共构造器(仅 public 修饰):getConstructors()
```java
Constructor[] constructors = clazz.getConstructors();System.out.println("公共构造器:" + Arrays.toString(constructors));```
3.4 获取类的其他信息(父类、接口等)
```java
// 获取父类
Class superClass = clazz.getSuperclass();System.out.println("父类:" + superClass.getName());// 获取实现的接口Class[] interfaces = clazz.getInterfaces();System.out.println("实现的接口:" + Arrays.toString(interfaces));```
四、反射进阶:动态操作类的成员
4.1 动态创建对象(通过构造器)
```java
import java.lang.reflect.Constructor;public class CreateObjectDemo {public static void main(String[] args) throws Exception {Class clazz = Class.forName("com.qcby.Animal");// 方式1:调用无参构造器(类必须有无参构造器)Animal animal = (Animal) clazz.newInstance(); // JDK 9 后已过时,推荐方式2// 方式2:通过 Constructor 对象调用指定构造器Constructor constructor = clazz.getConstructor(String.class, Integer.class, String.class, String.class);Animal animal2 = (Animal) constructor.newInstance("小花", 2, "雌性", "白色");}}```
4.2 动态调用方法
```java
import java.lang.reflect.Method;public class InvokeMethodDemo {public static void main(String[] args) throws Exception {Class clazz = Class.forName("com.qcby.Animal");Animal animal = (Animal) clazz.getConstructor().newInstance();// 获取方法:第一个参数是方法名,后面是参数类型Method eatMethod = clazz.getDeclaredMethod("eat", String.class);// 调用方法:第一个参数是对象(静态方法传 null),后面是方法参数eatMethod.invoke(animal, "鱼肉"); // 输出:鱼肉~// 调用静态方法Method staticEatMethod = clazz.getDeclaredMethod("eat", String.class);staticEatMethod.invoke(null, "猫粮"); // 输出:猫粮~}}```
4.3 动态修改变量
```java
import java.lang.reflect.Field;public class ModifyFieldDemo {public static void main(String[] args) throws Exception {Class clazz = Class.forName("com.qcby.Animal");Animal animal = (Animal) clazz.getConstructor().newInstance();// 获取变量(支持私有变量,需设置可访问)Field nameField = clazz.getDeclaredField("name");nameField.setAccessible(true); // 突破私有访问限制nameField.set(animal, "小黑"); // 修改变量值System.out.println(animal.getName()); // 输出:小黑}}```
五、反射的优缺点与注意事项
5.1 优点
- **灵活性高**:可在运行时动态操作类,适应框架的可扩展性需求;
- **解耦**:避免硬编码,降低类之间的依赖;
- **元编程支持**:实现“代码生成代码”的高级功能。
5.2 缺点
- **性能开销**:反射操作比直接调用慢(需解析类结构、检查访问权限等);
- **安全性**:可突破访问权限(如修改私有变量),破坏封装性;
- **代码可读性**:反射代码通常比直接调用更晦涩。
5.3 注意事项
- 频繁使用反射的场景(如框架核心逻辑),可通过**缓存 Class、Method、Field 对象**来优化性能;
- 操作私有成员时,需调用 setAccessible(true),但要谨慎使用(避免破坏封装);
- 反射异常(如 ClassNotFoundException、NoSuchMethodException)需显式处理。
六、总结:反射机制核心要点
1. **反射的本质**:运行时获取类信息并动态操作类成员的能力,基于 Class 对象实现;
2. **Class 对象的获取**:Class.forName()、类名.class、对象.getClass(),三者获取的是同一个对象;
3. **反射实战**:可获取类的变量、方法、构造器等信息,也可动态创建对象、调用方法、修改变量;
4. **优缺点**:灵活性高但性能开销大,适合框架开发,普通业务代码需谨慎使用。
反射是 Java 进阶的重要知识点,是理解 Spring、MyBatis 等框架的基础。掌握反射后,你将能更深入地理解 Java 程序的运行机制,也能更灵活地应对复杂的开发场景。
