Java笔记——反射

在 Java 的世界里,反射(Reflection)是一把双刃剑。它赋予了程序在运行时动态获取类的信息、创建对象、调用方法、访问字段的能力,让 Java 从编译时静态语言获得了动态语言的灵活性。许多框架(如 Spring、MyBatis、Hibernate)都离不开反射。但反射也带来了性能损耗和安全隐患。本文将带你全面了解 Java 反射的核心概念、使用方式、应用场景以及注意事项。

一、什么是反射?

反射是指程序在运行期可以访问、检测和修改它自身状态或行为的一种能力。通过反射,我们可以:

  • 获取任意类的名称、包信息、父类、接口等。

  • 动态创建对象实例。

  • 获取并调用任意方法(包括私有方法)。

  • 访问和修改字段的值(包括私有字段)。

  • 操作数组。

Java 反射 API 主要位于 java.lang.reflect 包中。

二、反射的核心类

类/接口 作用
Class 代表一个类或接口,反射的入口
Constructor 代表构造方法
Method 代表普通方法
Field 代表成员变量(字段)
Array 提供动态创建和访问数组的静态方法
Modifier 提供解析访问修饰符的工具方法
Parameter 代表方法参数(JDK 8+)

三、获取 Class 对象

要使用反射,首先需要获取目标类的 Class 对象。有以下三种方式:

3.1 通过类名.class

java 复制代码
Class<?> clazz1 = String.class;
Class<?> clazz2 = int.class; // 基本类型也有 Class 对象

3.2 通过对象的 getClass() 方法

java 复制代码
String str = "hello";
Class<?> clazz = str.getClass();

3.3 通过 Class.forName() 全限定类名

java 复制代码
Class<?> clazz = Class.forName("java.util.ArrayList");

注意:这种方式会触发类的静态初始化块(如果类还未加载)。

四、通过反射创建对象

4.1 使用无参构造器

java 复制代码
Class<?> clazz = Class.forName("java.util.ArrayList");
Object obj = clazz.newInstance();  // 已过时,但依然可用

推荐使用 Constructor 来创建:

java 复制代码
Constructor<?> constructor = clazz.getConstructor();
Object obj = constructor.newInstance();

4.2 使用有参构造器

java 复制代码
Class<?> clazz = Class.forName("java.lang.String");
// 获取参数类型为 String 的构造器
Constructor<?> constructor = clazz.getConstructor(String.class);
Object obj = constructor.newInstance("hello");

五、通过反射调用方法

5.1 调用公共方法

java 复制代码
public class Person {
    public void sayHello(String name) {
        System.out.println("Hello, " + name);
    }
}

// 反射调用
Class<?> clazz = Person.class;
Object person = clazz.newInstance();
Method method = clazz.getMethod("sayHello", String.class);
method.invoke(person, "World");

5.2 调用私有方法

java 复制代码
public class Calculator {
    private int add(int a, int b) {
        return a + b;
    }
}

// 反射调用私有方法
Calculator calc = new Calculator();
Method method = Calculator.class.getDeclaredMethod("add", int.class, int.class);
method.setAccessible(true);  // 压制 Java 访问检查
int result = (int) method.invoke(calc, 3, 5);
System.out.println(result);  // 8

六、通过反射访问字段

6.1 访问公共字段

java 复制代码
public class User {
    public String name;
}

User user = new User();
Field field = User.class.getField("name");
field.set(user, "Alice");
System.out.println(field.get(user));  // Alice

6.2 访问私有字段

java 复制代码
public class Student {
    private int age;
}

Student student = new Student();
Field field = Student.class.getDeclaredField("age");
field.setAccessible(true);
field.set(student, 18);
System.out.println(field.get(student));  // 18

七、操作数组

Array 类提供了一组静态方法,可以动态创建和访问数组。

java 复制代码
// 创建一个 int 数组,长度为 5
Object array = Array.newInstance(int.class, 5);
// 设置索引 0 处的值为 10
Array.set(array, 0, 10);
// 获取索引 0 处的值
int value = Array.getInt(array, 0);
System.out.println(value);  // 10

八、反射的应用场景

8.1 框架开发

Spring 的 IoC 容器通过反射创建 Bean 实例并注入依赖;MyBatis 通过反射将数据库查询结果映射到 Java 对象;JUnit 通过反射执行测试方法。

8.2 动态代理

JDK 动态代理基于接口生成代理类,底层使用了反射来调用目标方法。

8.3 注解处理

在运行时读取注解信息,通常结合反射实现。例如,Spring MVC 的 @RequestMapping 注解解析。

8.4 工具类与调试

开发通用工具(如对象深拷贝、JSON 序列化)时,反射可以避免针对每种类型编写重复代码。

九、反射的性能问题与优化

反射由于涉及动态解析、安全检查(即使 setAccessible 也还是有一定开销),性能远低于直接调用。在性能敏感的场景下,可以采取以下优化措施:

  1. 缓存反射对象MethodFieldConstructor 对象可以缓存,避免重复获取。

  2. 关闭访问检查setAccessible(true) 可以略微提升速度。

  3. 使用 MethodHandle(JDK 7+):提供比反射更快的动态调用方式。

  4. 避免频繁调用反射:如果反射调用是热点代码,考虑使用代码生成或缓存代理。

性能对比示例(简单测试):

java 复制代码
// 直接调用
for (int i = 0; i < 1_000_000; i++) {
    obj.method();
}
// 反射调用(未缓存)
for (int i = 0; i < 1_000_000; i++) {
    Method m = obj.getClass().getMethod("method");
    m.invoke(obj);
}
// 反射调用(缓存 Method 对象)
Method m = obj.getClass().getMethod("method");
for (int i = 0; i < 1_000_000; i++) {
    m.invoke(obj);
}

实际测试中,缓存后的反射调用大约比直接调用慢 3-5 倍,而未缓存的反射调用则慢得多。

十、反射的安全性问题

  • 破坏封装性 :通过 setAccessible(true) 可以访问私有成员,打破了类的封装原则,可能导致不可预知的行为。

  • 安全限制:在安全管理器(SecurityManager)启用的情况下,某些反射操作可能被禁止。

  • 框架使用场景:通常只在框架内部使用反射,业务代码应尽量避免直接使用反射,以保持代码的清晰和可维护性。

十一、总结

Java 反射机制是一种强大的工具,它使程序具备了动态性,是框架和基础库不可或缺的部分。然而,反射也是一把双刃剑:

  • 优点:提供了极高的灵活性,支持动态代理、依赖注入、注解处理等高级特性。

  • 缺点:性能损耗、破坏封装、可能引发安全问题、代码可读性降低。

在实际开发中,建议只在确实需要动态处理时才使用反射,并遵循以下原则:

  1. 缓存反射对象以减少性能开销。

  2. 尽量使用 MethodHandle 替代反射(如果性能是关键)。

  3. 避免在业务代码中随意使用反射,保持代码的简洁和可维护性。

理解反射的原理,不仅有助于写出更优雅的代码,也能帮助你更深入地理解 Spring、MyBatis 等主流框架的实现。希望本文能为你揭开反射的神秘面纱,让你在 Java 编程之路上走得更远。

相关推荐
左左右右左右摇晃2 小时前
Java笔记——IO
java·开发语言·笔记
萍萍学习2 小时前
蓝桥杯JAVA-3
java·职场和发展·蓝桥杯
西野.xuan2 小时前
内存布局(堆vs栈)一篇详解!!
java·数据结构·算法
无心水2 小时前
时间处理工程落地指南:数据库/日志/API/定时任务
java·大数据·数据库·日志·分布式架构·utc·gmt
Byron__2 小时前
HashSet/LinkedHashSet/TreeSet 原理深度解析
java·开发语言
小陈工3 小时前
2026年3月24日技术资讯洞察:边缘AI商业化,Java26正式发布与开源大模型成本革命
java·运维·开发语言·人工智能·python·容器·开源
haibindev3 小时前
把近5万个源文件喂给AI之前,我先做了一件事
java·前端·c++·ai编程·代码审计·架构分析
yymboss3 小时前
【JavaEE】Spring Boot 项目创建
java·spring boot·java-ee
sxhcwgcy3 小时前
快速在本地运行SpringBoot项目的流程介绍
java·spring boot·后端