【Java 基础】Java 反射机制详解:从原理到实战
摘要
反射是 Java 语言中一个非常重要的特性,也是 Spring、MyBatis 等主流框架实现的底层核心技术。本文将从反射的定义、核心原理、核心 API、使用步骤、应用场景、优缺点以及实战案例等多个维度,带你彻底搞懂 Java 反射机制,帮你完成作业的同时,为后续学习框架打下坚实基础。
一、什么是 Java 反射?
1. 定义
Java 反射机制(Reflection)是指在程序运行状态中:
对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性。
这种动态获取类信息以及动态调用对象方法的功能,就叫做 Java 语言的反射机制。
2. 通俗理解
正常情况下,我们使用一个类的流程是:先知道类名 → new创建对象 → 调用对象的方法 / 属性,这是 “静态” 的,编译期就已经确定了要操作的类。
而反射是 “动态” 的,编译期我们可能不知道要操作哪个类,只有在运行时才拿到类的全限定名,通过反射直接操作它的对象、方法和属性,哪怕这些成员是私有的。相当于给程序开了个 “后门”,可以直接看到类的内部结构。
二、反射的核心原理:Class 对象
要理解反射,必须先搞懂Class 对象,它是反射的入口。
1.什么是 Class 对象?
当 Java 文件被编译成.class字节码文件后,类加载器(ClassLoader)会把这个.class文件加载到 JVM 中,然后为每个类在内存中创建一个对应的java.lang.Class对象。
这个Class对象包含了该类的所有结构信息:类名、属性、方法、构造器、父类、实现的接口等。反射操作的所有功能,都是通过这个Class对象来完成的。
关键点:每个类在 JVM 中只有一个唯一的Class对象,不管你new多少个该类的对象,它们的Class对象都是同一个。
2.获取 Class 对象的三种方式
(1)类名.class(静态方式)
代码示例:Class clazz = User.class;
适用场景:编译期已知类名,最安全、性能最好
(2)对象.getClass ()
代码示例:User user = new User();Class<? extends User> clazz = user.getClass();
适用场景:已有对象实例,获取其 Class 对象
(3)Class.forName(“全类名”)
代码示例:Class<>clazz=Class.forName(“com.example.User”);
适用场景: 运行时动态加载类(最常用,适合框架场景)
示例代码:
public class ReflectTest {
public static void main(String[] args) throws ClassNotFoundException {
// 方式1:类名.class
Class<User> clazz1 = User.class;
// 方式2:对象.getClass()
User user = new User();
Class<? extends User> clazz2 = user.getClass();
// 方式3:Class.forName("全类名")
Class<?> clazz3 = Class.forName("com.example.reflection.User");
// 验证三个Class对象是否为同一个
System.out.println(clazz1 == clazz2); // true
System.out.println(clazz1 == clazz3); // true
}
}
三、反射的核心 API
Java 反射相关的 API 都在java.lang.reflect包下,核心类如下:
(1)java.lang.Class 作用:代表类的字节码对象,反射操作的入口
(2)java.lang.reflect.Constructor 作用:代表类的构造方法,用于创建对象实例
(3)java.lang.reflect.Field 作用:代表类的成员变量(属性),用于获取 / 修改属性值
(4)java.lang.reflect.Method 作用:代表类的成员方法,用于调用方法
(5)java.lang.reflect.Modifier 作用:代表修饰符(public、private、static 等),用于判断成员的访问权限
四、反射的基础使用步骤(附完整代码)
我们先定义一个简单的实体类User,作为反射操作的目标类:
package com.example.reflection;
public class User {
// 私有属性
private String name;
public int age;
// 无参构造方法
public User() {
System.out.println("无参构造方法执行了");
}
// 有参构造方法
public User(String name, int age) {
this.name = name;
this.age = age;
System.out.println("有参构造方法执行了");
}
// 私有方法
private String sayHello(String message) {
return "Hello, " + message + "! My name is " + name + ", I'm " + age + " years old.";
}
// 公共getter/setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}
接下来我们通过反射,一步步实现:获取 Class 对象 → 创建对象实例 → 操作成员变量 → 调用成员方法。
步骤 1:创建对象实例
反射创建对象有两种方式:通过无参构造、通过有参构造。
public class ReflectCreateObject {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.example.reflection.User");
// 方式1:通过无参构造创建对象(推荐)
Constructor<?> noArgConstructor = clazz.getConstructor();
Object user1 = noArgConstructor.newInstance();
System.out.println(user1);
// 方式2:通过有参构造创建对象
Constructor<?> argConstructor = clazz.getConstructor(String.class, int.class);
Object user2 = argConstructor.newInstance("张三", 18);
System.out.println(user2);
}
}
步骤 2:操作成员变量(Field)
反射可以获取并修改类的属性,包括私有属性:
public class ReflectFieldTest {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.example.reflection.User");
Constructor<?> constructor = clazz.getConstructor();
Object user = constructor.newInstance();
// 1. 获取所有声明的属性(包括私有、保护、默认,不包括父类的)
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println("声明的属性:" + field.getName());
}
// 2. 获取并修改私有属性name
Field nameField = clazz.getDeclaredField("name");
// 关键:私有属性需要设置可访问,否则会抛出IllegalAccessException
nameField.setAccessible(true);
// 修改属性值
nameField.set(user, "李四");
// 获取属性值
String name = (String) nameField.get(user);
System.out.println("修改后的name值:" + name);
// 3. 获取并修改公共属性age
Field ageField = clazz.getField("age");
ageField.set(user, 20);
int age = (int) ageField.get(user);
System.out.println("修改后的age值:" + age);
}
}
步骤 3:调用成员方法(Method)
反射可以调用类的方法,包括私有方法:
public class ReflectMethodTest {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.example.reflection.User");
Constructor<?> constructor = clazz.getConstructor();
Object user = constructor.newInstance();
// 1. 调用公共setter方法
Method setNameMethod = clazz.getMethod("setName", String.class);
setNameMethod.invoke(user, "王五");
// 2. 调用公共getter方法
Method getNameMethod = clazz.getMethod("getName");
String name = (String) getNameMethod.invoke(user);
System.out.println("调用getName返回:" + name);
// 3. 调用私有方法sayHello
Method sayHelloMethod = clazz.getDeclaredMethod("sayHello", String.class);
// 私有方法需要设置可访问
sayHelloMethod.setAccessible(true);
// 调用方法,参数:对象实例、方法参数
String result = (String) sayHelloMethod.invoke(user, "反射");
System.out.println("调用私有方法返回:" + result);
}
}
五、反射的常见应用场景
反射虽然在业务代码中用得不多,但却是很多框架的底层核心技术,常见场景包括:
1.主流框架开发
—Spring 的 IOC 容器:通过配置文件的全类名,用反射创建对象并实现依赖注入;
—Spring 的 AOP:通过动态代理(基于反射)实现方法增强;
—MyBatis:通过反射创建 Mapper 接口的代理对象,执行 SQL 语句。
2.动态代理:JDK 动态代理就是基于反射实现的,用于生成接口的代理对象。
**3.注解处理:**很多框架的注解解析(如 SpringMVC 的@RequestMapping)都是通过反射实现的。
**4.JDBC 驱动加载:**早期 JDBC 通过Class.forName(“com.mysql.cj.jdbc.Driver”)加载数据库驱动。
**5.通用工具类:**比如BeanUtils,通过反射实现对象之间的属性拷贝。
六、反射的优缺点与注意事项
优点
1.动态性强:可以在运行时动态操作类和对象,不需要提前知道类的信息,提高了代码的灵活性和可扩展性;
2.降低耦合度:框架通过反射实现了解耦,比如 Spring 的 IOC 容器,不需要在业务代码中硬编码创建对象;
3.突破封装限制:可以直接访问和修改类的私有成员,实现一些特殊需求。
缺点
1.性能较低:反射是动态解析的,JVM 无法对反射操作进行优化,比直接调用慢很多;
2.破坏封装性:可以直接访问私有成员,违反了面向对象的封装原则,存在安全隐患;
3.可读性差:反射代码比直接调用难理解,调试也更困难;
存在安全风险:滥用反射可能会修改类的内部状态,导致程序出现不可预期的错误。
注意事项
1.尽量减少反射的使用,只在框架底层等必要场景使用;
2.使用setAccessible(true)时要谨慎,避免破坏类的封装性;
3.反射操作需要处理异常(如ClassNotFoundException、NoSuchMethodException、IllegalAccessException等);
4.不要滥用反射修改类的私有状态,可能会导致程序异常。
七、实战案例:用反射实现通用属性拷贝工具
很多时候我们需要把一个对象的属性拷贝到另一个对象(比如 DTO 转 PO),用反射可以实现一个通用工具类,不需要重复写 get/set 代码:
import java.lang.reflect.Field;
public class BeanCopyUtils {
/**
* 通用对象属性拷贝工具类
* @param source 源对象
* @param target 目标对象
* @throws Exception 异常
*/
public static void copyProperties(Object source, Object target) throws Exception {
// 获取源对象和目标对象的Class
Class<?> sourceClazz = source.getClass();
Class<?> targetClazz = target.getClass();
// 获取源对象的所有声明属性
Field[] sourceFields = sourceClazz.getDeclaredFields();
// 遍历源对象的属性
for (Field sourceField : sourceFields) {
sourceField.setAccessible(true);
String fieldName = sourceField.getName();
Object value = sourceField.get(source);
// 尝试在目标对象中找到同名属性
Field targetField;
try {
targetField = targetClazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
// 目标对象没有该属性,跳过
continue;
}
// 检查属性类型是否兼容
if (targetField.getType().equals(sourceField.getType())) {
targetField.setAccessible(true);
targetField.set(target, value);
}
}
}
// 测试
public static void main(String[] args) throws Exception {
// 源对象
User user = new User("张三", 18);
// 目标DTO对象
UserDTO userDTO = new UserDTO();
// 拷贝属性
copyProperties(user, userDTO);
System.out.println(userDTO);
}
}
// 目标DTO类
class UserDTO {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "UserDTO{name='" + name + "', age=" + age + "}";
}
}
八、总结
反射机制是 Java 语言的高级特性,也是很多主流框架的底层基础。虽然平时写业务代码用得不多,但理解反射对后续学习 Spring、MyBatis 等框架至关重要。
更多推荐


所有评论(0)