Java 反射详解
Java 反射(Reflection)是 Java 语言的核心特性之一,它允许程序在运行时获取类的信息、操作类的属性和方法,甚至访问私有成员。这种动态性使得反射成为框架开发(如 Spring、MyBatis)、注解处理、动态代理等场景的基石。本文将从反射的基本概念、核心 API、使用场景到优缺点,全面解析 Java 反射机制。
方式 1:
方式 2:
方式 3:
一、什么是反射?
反射是指程序在运行状态下,对于任意一个类,都能知道它的所有属性和方法;对于任意一个对象,都能调用它的任意方法和属性。这种 “看透” 类信息并动态操作的能力,打破了 Java 编译期的类型检查限制,赋予程序更高的灵活性。
简单来说,反射让 Java 从 “静态语言” 具备了部分 “动态语言” 的特性 —— 不需要在编译期知道类的具体信息,就能在运行时动态操作。
二、反射的核心 API
Java 反射的核心功能由
java.lang.reflect 包提供,配合 java.lang.Class 类实现。关键类包括:| 类 / 接口 | 作用 |
|---|---|
Class |
代表类的字节码对象,是反射的入口 |
Constructor |
代表类的构造方法,用于创建对象 |
Method |
代表类的成员方法,用于调用方法 |
Field |
代表类的成员变量,用于获取 / 修改属性值 |
Modifier |
解析类 / 方法 / 字段的修饰符(如 public、private) |
三、反射的基本使用步骤
反射的核心操作围绕
Class 对象展开,步骤可概括为:获取 Class 对象 → 操作类的成员(构造方法 / 方法 / 字段)。1. 获取 Class 对象(反射的入口)
Class 类是反射的基础,每个类在 JVM 中都有且仅有一个 Class 对象,它存储了该类的所有信息(类名、父类、方法、字段等)。获取 Class 对象有 3 种方式:方式 1:Class.forName("全类名")
通过类的全限定名(包名 + 类名)获取,最常用(尤其框架配置)。
// 获取 java.lang.String 的 Class 对象
Class<?> cls = Class.forName("java.lang.String");
方式 2:类名.class
通过类的字面量获取,编译期即可确定类型,更高效。
// 获取 String 的 Class 对象
Class<String> cls = String.class;
方式 3:对象.getClass()
通过实例对象获取,适用于已有对象的场景。
String str = "hello";
Class<? extends String> cls = str.getClass();
2. 通过 Class 对象操作类成员
(1)获取构造方法并创建对象
Constructor 类用于操作构造方法,支持获取所有构造方法(包括私有)并创建实例。import java.lang.reflect.Constructor;
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
// 1. 获取 User 类的 Class 对象
Class<User> userClass = User.class;
// 2. 获取无参构造方法(public)
Constructor<User> constructor1 = userClass.getConstructor();
User user1 = constructor1.newInstance(); // 调用无参构造创建对象
// 3. 获取有参构造方法(public User(String name, int age))
Constructor<User> constructor2 = userClass.getConstructor(String.class, int.class);
User user2 = constructor2.newInstance("张三", 20); // 传入参数创建对象
// 4. 获取私有构造方法(private User(int id))
Constructor<User> constructor3 = userClass.getDeclaredConstructor(int.class);
constructor3.setAccessible(true); // 暴力访问私有构造(关键!)
User user3 = constructor3.newInstance(1001); // 创建对象
}
}
class User {
private int id;
private String name;
private int age;
// 无参构造
public User() {}
// 有参构造(public)
public User(String name, int age) {
this.name = name;
this.age = age;
}
// 私有构造
private User(int id) {
this.id = id;
}
}
关键方法:
getConstructor(Class<?>... parameterTypes):获取 public 构造方法(指定参数类型)getDeclaredConstructor(Class<?>... parameterTypes):获取所有构造方法(包括 private)setAccessible(true):关闭访问检查,允许访问私有成员(核心!)
(2)获取成员方法并调用
Method 类用于操作成员方法,支持调用任意方法(包括私有)并传递参数。import java.lang.reflect.Method;
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
User user = new User("李四", 25);
Class<? extends User> userClass = user.getClass();
// 1. 调用 public 方法:public String getName()
Method getNameMethod = userClass.getMethod("getName");
String name = (String) getNameMethod.invoke(user); // 调用方法,传入实例对象
System.out.println("姓名:" + name); // 输出:李四
// 2. 调用带参 public 方法:public void setAge(int age)
Method setAgeMethod = userClass.getMethod("setAge", int.class);
setAgeMethod.invoke(user, 30); // 传入参数 30
System.out.println("修改后年龄:" + user.getAge()); // 输出:30
// 3. 调用私有方法:private void printInfo()
Method printInfoMethod = userClass.getDeclaredMethod("printInfo");
printInfoMethod.setAccessible(true); // 允许访问私有方法
printInfoMethod.invoke(user); // 调用私有方法,输出:User{name='李四', age=30}
}
}
class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
private void printInfo() {
System.out.println("User{name='" + name + "', age=" + age + "}");
}
}
关键方法:
getMethod(String name, Class<?>... parameterTypes):获取 public 方法(指定方法名和参数类型)getDeclaredMethod(String name, Class<?>... parameterTypes):获取所有方法(包括 private)invoke(Object obj, Object... args):调用方法(obj 为实例对象,args 为方法参数)
(3)获取成员变量并修改值
Field 类用于操作成员变量,支持获取和修改任意字段(包括私有)的值。import java.lang.reflect.Field;
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
User user = new User("王五", 30);
Class<? extends User> userClass = user.getClass();
// 1. 获取 public 字段(实际很少用,通常字段都是 private)
// 此处假设 User 有 public String address 字段
// Field addressField = userClass.getField("address");
// 2. 获取 private 字段:name
Field nameField = userClass.getDeclaredField("name");
nameField.setAccessible(true); // 允许访问私有字段
String originalName = (String) nameField.get(user); // 获取字段值
System.out.println("原始姓名:" + originalName); // 输出:王五
nameField.set(user, "赵六"); // 修改字段值
System.out.println("修改后姓名:" + user.getName()); // 输出:赵六
// 3. 获取 private 字段:age 并修改
Field ageField = userClass.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(user, 35);
System.out.println("修改后年龄:" + user.getAge()); // 输出:35
}
}
class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
关键方法:
getField(String name):获取 public 字段getDeclaredField(String name):获取所有字段(包括 private)get(Object obj):获取字段值(obj 为实例对象)set(Object obj, Object value):修改字段值
四、反射的高级应用场景
反射的动态性使其在以下场景中不可或缺:
1. 框架开发(核心场景)
几乎所有主流 Java 框架(Spring、MyBatis、Hibernate)都依赖反射实现核心功能:
- Spring IOC:通过配置文件(如 XML)中的类名,反射创建对象并注入依赖(
Class.forName(类名).newInstance())。 - MyBatis:通过 Mapper 接口的全类名,反射生成代理对象,动态执行 SQL。
- JUnit:通过反射识别
@Test注解的方法并执行测试。
2. 动态代理
反射是动态代理的基础(如 JDK 动态代理)。代理模式中,代理类在运行时通过反射调用目标类的方法,实现增强逻辑(如日志、事务)。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 接口
interface UserService {
void save();
}
// 实现类
class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("保存用户数据");
}
}
// 动态代理处理器
class LogHandler implements InvocationHandler {
private Object target; // 目标对象
public LogHandler(Object target) {
this.target = target;
}
// 反射调用目标方法,并添加日志增强
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("日志:开始调用 " + method.getName() + " 方法");
Object result = method.invoke(target, args); // 反射调用目标方法
System.out.println("日志:结束调用 " + method.getName() + " 方法");
return result;
}
}
public class ProxyDemo {
public static void main(String[] args) {
// 创建目标对象
UserService userService = new UserServiceImpl();
// 创建代理对象(通过反射生成)
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
new LogHandler(userService)
);
proxy.save(); // 调用代理方法,实际通过反射执行目标方法
}
}
输出:
日志:开始调用 save 方法
保存用户数据
日志:结束调用 save 方法
3. 注解处理
注解(Annotation)本身不具备功能,需通过反射解析注解并执行逻辑。例如自定义一个
@Log 注解,通过反射识别并打印日志:import java.lang.annotation.*;
import java.lang.reflect.Method;
// 自定义注解
@Target(ElementType.METHOD) // 作用于方法
@Retention(RetentionPolicy.RUNTIME) // 运行时保留(反射可获取)
@interface Log {
String value() default "执行方法";
}
class UserService {
@Log("保存用户") // 使用注解
public void save() {
System.out.println("保存用户数据");
}
}
public class AnnotationDemo {
public static void main(String[] args) throws Exception {
UserService service = new UserService();
Class<? extends UserService> serviceClass = service.getClass();
Method saveMethod = serviceClass.getMethod("save");
// 反射检查方法是否有 @Log 注解
if (saveMethod.isAnnotationPresent(Log.class)) {
Log logAnnotation = saveMethod.getAnnotation(Log.class);
System.out.println("日志:" + logAnnotation.value()); // 输出:日志:保存用户
}
saveMethod.invoke(service); // 调用方法
}
}
五、反射的优缺点
优点
- 动态性:打破编译期限制,运行时动态操作类,适合框架开发(无需硬编码类信息)。
- 灵活性:可适配不同类的通用逻辑(如 ORM 框架的对象映射)。
- 扩展性:通过反射加载外部类(如插件),增强程序扩展性。
缺点
- 性能损耗:反射操作绕过编译期优化,比直接调用慢 10-100 倍(频繁调用需缓存
Class/Method对象优化)。 - 破坏封装:
setAccessible(true)可访问私有成员,违反类的封装设计。 - 可读性差:反射代码较繁琐,且逻辑隐藏在运行时,调试和维护难度高。
- 安全风险:若被恶意使用,可能通过反射调用私有方法或修改敏感字段(需配合安全管理器限制)。
六、反射的性能优化
反射的性能问题主要源于频繁获取
Class、Method 等对象。优化方式:-
缓存反射对象:将
Class、Method、Field缓存到Map中,避免重复获取。// 缓存 Method 对象示例 private static final Map<String, Method> methodCache = new HashMap<>(); public static Method getCachedMethod(Class<?> cls, String methodName, Class<?>... params) throws NoSuchMethodException { String key = cls.getName() + "#" + methodName + Arrays.toString(params); if (!methodCache.containsKey(key)) { methodCache.put(key, cls.getDeclaredMethod(methodName, params)); } return methodCache.get(key); } -
减少
setAccessible(true)调用:该方法会触发安全检查,缓存已设置访问权限的对象。 -
优先使用接口或父类:反射调用接口方法比具体类方法更快(JVM 优化)。
七、总结
反射是 Java 动态编程的核心,它赋予程序在运行时操作类的能力,是框架、动态代理、注解等技术的基础。但反射也存在性能损耗和封装性问题,需在灵活性和安全性之间权衡。
最佳实践:
- 框架开发中合理使用反射(如 Spring IOC),提升通用性;
- 业务代码中尽量避免反射,优先使用直接调用;
- 必须使用反射时,通过缓存和权限控制优化性能与安全。
浙公网安备 33010602011771号