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. 缓存反射对象 :Method、Field、Constructor 对象可以缓存,避免重复获取。

  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 编程之路上走得更远。

相关推荐
BioRunYiXue17 分钟前
使用docker分析高通量测序数据
java·数据库·人工智能·spring cloud·docker·tomcat·html
Shaoshing1 小时前
缓存一致问题及常见解决方案
java·缓存
聚美智数1 小时前
文档转换 API‑文档转 PDF‑PDF 转文档‑异步文档处理‑文件格式迁移‑转换结果回调推送 API 接口介绍
android·java·pdf
从零数据分析1 小时前
零基础学SQL 23:和上月比总对不上?LAG/LEAD 一步取出上一次(附 4 个模板)
java·数据库·sql·mysql·oracle·sqlite·database
yuzhiboyouye1 小时前
com.fasterxml.jackson.annotation 里最常用高频的注解,可以按“控制字段、控制格式、控制结构、控制流程”来记
java·开发语言
苜蓿muxu1 小时前
简述 ConcurrentHashMap 和 CopyOnWriteArrayList 的区别以及各自适用场景
java·编程语言
9527!到!2 小时前
Springboot @value读取配置参数踩坑&推荐解决方案
java·spring boot·spring
十年Java程序媛2 小时前
Java 面试常问 this、super:构造方法调用顺序避坑
java·开发语言·spring boot·面试·个人开发
337802 小时前
5分钟搞懂Spring的IoC和Bean管理
java
学到老活到老2 小时前
类已经加载了,为什么静态代码还没执行?
java·jvm