常德市网站建设_网站建设公司_悬停效果_seo优化
2026/3/2 15:32:21 网站建设 项目流程

Java反射(Reflection)核心知识点

1. 什么是反射?(The "What")

Java反射机制是指在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性。这种动态获取信息以及动态调用对象方法的功能被称为Java语言的反射机制。

简单来说,反射赋予了Java代码一种“反思”或“自省”的能力。正常情况下,我们是在编译时就确定了要使用的类和要调用的方法。而通过反射,我们可以在运行时根据字符串形式的类名或方法名,来动态地加载类、创建对象、调用方法或访问字段。

核心入口:所有反射操作的入口点都是 java.lang.Class 类的实例。一个加载到JVM中的类,有且只有一个对应的Class对象。

2. 如何获取Class对象?

主要有三种方式:

  1. 通过类名获取ClassName.class

    Class<String> clazz = String.class; // 直接通过类名.class 获取 Class 对象
    
  2. 通过对象实例获取instance.getClass()

    String str = "Hello";
    Class<?> clazz = str.getClass(); // 通过已存在的对象实例调用 getClass() 方法获取 Class 对象
    
  3. 通过类的全限定名获取Class.forName("full.package.ClassName") (常用于动态加载)

    try {Class<?> clazz = Class.forName("java.lang.String"); // 通过类的全限定名字符串加载类并获取 Class 对象
    } catch (ClassNotFoundException e) {e.printStackTrace(); // 处理类未找到异常
    }
    

3. 反射的核心API和基本用法

获取Class对象后,就可以使用 java.lang.reflect 包中的类来操作它了,主要包括 Constructor, Method, Field

3.1 创建实例 (Constructors)

  • getConstructors(): 获取所有public构造方法。
  • getDeclaredConstructors(): 获取所有已声明的构造方法(包括private)。
  • getConstructor(Class<?>... parameterTypes): 获取指定的public构造方法。
  • getDeclaredConstructor(Class<?>... parameterTypes): 获取指定的构造方法。

示例:

// 获取无参构造方法来创建实例
Class<User> userClass = User.class; // 1. 获取 User 类的 Class 对象
// 2. 获取无参构造方法(Constructor 对象)
// 3. 调用 newInstance() 创建 User 实例
User user = userClass.getDeclaredConstructor().newInstance(); // 获取有参构造方法来创建实例
// 1. 获取指定参数类型(String, int)的构造方法
Constructor<User> constructor = userClass.getConstructor(String.class, int.class);
// 2. 调用 newInstance() 并传入参数,创建 User 实例
User user2 = constructor.newInstance("Alice", 25);

3.2 调用方法 (Methods)

  • getMethods(): 获取所有public方法(包括从父类继承的)。
  • getDeclaredMethods(): 获取所有已声明的方法(不包括继承的)。
  • getMethod(String name, Class<?>... parameterTypes): 获取指定的public方法。
  • getDeclaredMethod(String name, Class<?>... parameterTypes): 获取指定的方法。

示例:

Class<User> userClass = User.class;
User user = userClass.getDeclaredConstructor().newInstance(); // 实例化 User 对象 (假设 User 有无参构造器)// 获取并调用public方法
// 1. 获取名为 "setName",参数类型为 String 的 public 方法
Method setNameMethod = userClass.getMethod("setName", String.class);
// 2. 调用 invoke(),传入对象实例和方法参数
setNameMethod.invoke(user, "Bob"); // 等同于 user.setName("Bob");// 获取并调用private方法
// 1. 获取名为 "privateHello",无参数的 private 方法
Method privateMethod = userClass.getDeclaredMethod("privateHello");
// 2. 关键一步:解除私有访问限制,使其可被反射调用
privateMethod.setAccessible(true); 
// 3. 调用 invoke(),传入对象实例
privateMethod.invoke(user);

3.3 访问字段 (Fields)

  • getFields(): 获取所有public字段。
  • getDeclaredFields(): 获取所有已声明的字段。
  • getField(String name): 获取指定的public字段。
  • getDeclaredField(String name): 获取指定的字段。

示例:

Class<User> userClass = User.class;
User user = userClass.getDeclaredConstructor().newInstance(); // 实例化 User 对象// 获取并修改public字段
// 1. 获取名为 "publicAge" 的 public 字段
Field publicField = userClass.getField("publicAge");
// 2. 调用 set(),传入对象实例和新值,修改字段
publicField.set(user, 30);// 获取并修改private字段
// 1. 获取名为 "name" 的 private 字段
Field privateField = userClass.getDeclaredField("name");
// 2. 关键一步:解除私有访问限制,使其可被反射访问和修改
privateField.setAccessible(true); 
// 3. 调用 set(),传入对象实例和新值,修改字段
privateField.set(user, "Charlie");
// 4. 调用 get(),传入对象实例,读取字段值
String name = (String) privateField.get(user); // 读取字段值

setAccessible(true) 是反射中一个非常强大的功能,它允许我们访问和修改类的私有成员。这破坏了类的封装性,需要谨慎使用。

4. 反射的典型应用场景 (框架剖析)

反射技术的核心在于运行时动态操作,因此它特别适合需要高度灵活性和通用性的底层框架,而非日常业务代码。可以记为“框架爱反射,封装为我破”。

下面我们通过几个主流框架的例子,来具体看看反射是如何工作的。

场景一:依赖注入框架 (如 Spring)

记忆点:Spring 用反射帮你 new 对象和 set 依赖,你只需要“贴标签” (@Component, @Autowired)。

你的代码:

@Component
public class OrderService {// 你只写了这一行,希望 Spring 帮你注入一个 UserService 实例@Autowiredprivate UserService userService;public void createOrder() {userService.createUser();// ...}
}

Spring 框架在幕后为你做的工作 (简化版):

  1. 扫描与实例化

    • Spring 启动时,扫描指定包路径下的所有 .class 文件。
    • 通过反射检查每个类上是否有 @Component 注解。
    • 如果发现 OrderService.class@Component 注解,就通过反射创建它的实例:
      // 伪代码
      Class<?> clazz = Class.forName("com.example.OrderService");
      Object orderServiceInstance = clazz.getDeclaredConstructor().newInstance();
      
  2. 依赖注入

    • Spring 继续扫描 orderServiceInstance 对象的所有字段。
    • 发现 userService 字段有 @Autowired 注解。
    • 由于 userService 字段是 private 的,Spring 无法直接访问。于是:
      // 伪代码
      Field field = clazz.getDeclaredField("userService"); // 获取私有字段
      field.setAccessible(true); // 关键:打破封装,授权访问// 从 Spring 容器中找到已经创建好的 UserService 实例
      Object userServiceInstance = springContext.getBean("userService"); // 将 UserService 实例注入到 OrderService 实例的私有字段中
      field.set(orderServiceInstance, userServiceInstance); 
      

如果没有反射:你就必须手动 new OrderService(),并且为 userService 字段提供一个 publicsetUserService() 方法,然后手动调用它来注入,代码会变得非常繁琐和耦合。

场景二:对象关系映射框架 (如 MyBatis)

记忆点:MyBatis 用反射帮你把数据库的一行行记录,自动“塞”进你的 Java 对象里。

你的代码:

// User 对象
public class User {private Integer id;private String userName;// (getters and setters...)
}// Mapper 接口
public interface UserMapper {@Select("SELECT id, user_name FROM user WHERE id = #{id}")User getUserById(Integer id);
}

你只需要调用 userMapper.getUserById(1),就能得到一个填充好数据的 User 对象。

MyBatis 框架在幕后为你做的工作 (简化版):

  1. 执行 SQL:MyBatis 执行 SQL 语句,从数据库获取一个 ResultSet (结果集),其中包含 iduser_name 两列。
  2. 创建对象:MyBatis 需要将这一行数据映射成一个 User 对象。它首先通过反射创建一个 User 的空实例:
    // 伪代码
    Class<?> clazz = User.class;
    Object userInstance = clazz.getDeclaredConstructor().newInstance();
    
  3. 遍历与填充
    • MyBatis 遍历 ResultSet 的列,比如第一列是 id,值为 1
    • 它会查找 User 类中是否有 id 这个字段(或者根据驼峰命名规则查找 Id)。
    • 找到 private Integer id; 字段后,通过反射为其赋值:
      // 伪代码
      Field field = clazz.getDeclaredField("id");
      field.setAccessible(true); // 授权访问私有字段
      field.set(userInstance, 1); // 将数据库的值 1 赋给 id 字段
      
    • user_name 字段也执行同样的操作,找到 userName 字段并赋值。

如果没有反射:你需要手动从 ResultSet 中通过 rs.getInt("id"), rs.getString("user_name") 取出每个值,然后手动调用 user.setId()user.setUserName(),编写大量重复的样板代码。

场景三:序列化/反序列化库 (如 Jackson/Gson)

记忆点:Jackson 用反射帮你把一串 JSON 文本,自动“变”成一个 Java 对象。

你的代码:

String json = "{\"id\":1, \"name\":\"Alice\"}";
ObjectMapper mapper = new ObjectMapper();
// 只需要一行代码
User user = mapper.readValue(json, User.class); 

Jackson 框架在幕后为你做的工作 (简化版):

  1. 解析 JSON:Jackson 解析 JSON 字符串,得到一个包含 id:1name:"Alice" 的键值对集合。
  2. 创建对象:通过反射创建 User 类的实例:
    // 伪代码
    Class<?> clazz = User.class;
    Object userInstance = clazz.getDeclaredConstructor().newInstance();
    
  3. 匹配与填充
    • Jackson 遍历 JSON 的每个键,比如键 "name"
    • 它会在 User 类中寻找一个名为 name 的字段。
    • 找到 private String name; 字段后,通过反射为其赋值:
      // 伪代码
      Field field = clazz.getDeclaredField("name");
      field.setAccessible(true); // 授权访问
      field.set(userInstance, "Alice"); // 将 JSON 的值 "Alice" 赋给 name 字段
      
    • id 字段也执行同样的操作。

如果没有反射:你需要手动解析 JSON,然后从解析结果中取出每个值,再手动调用对象的 setter 方法进行赋值,过程非常繁琐。

其他场景

  • 动态代理 (如 Spring AOP):在运行时动态地创建一个代理类,实现对目标对象的行为增强(如日志、事务),这是 AOP(面向切面编程)的基础。
  • 注解处理器 (如 JUnit):在运行时扫描类上的注解,并根据注解执行相应的逻辑。例如 JUnit 框架通过反射查找所有被 @Test 注记标记的方法并执行它们。

5. 反射的优缺点

优点

  • 动态性:极大地增加了程序的灵活性和扩展性,允许程序在运行时创建和控制任何类的对象,而无需在编译时进行硬编码。

缺点

  • 性能开销大:反射操作(如方法调用)比直接调用要慢得多。因为它涉及到动态类型检查、方法查找等一系列耗时操作,并且会绕过JVM的JIT编译优化。
  • 破坏封装性:通过setAccessible(true)可以访问类的私有成员,这违背了面向对象的封装原则。
  • 类型安全风险:反射操作在编译时不做类型检查,如果在运行时传入的类型或方法名不正确,很容易导致RuntimeException
  • 代码可读性差:大量使用反射的代码逻辑会变得复杂、晦涩,难以阅读和维护。

6. 结论

反射是一把强大的“双刃剑”。它为Java语言带来了无与伦比的动态能力,是许多现代框架的基石。但是,由于其性能开销和安全风险,在常规的应用程序开发中应谨慎使用。只有在确实需要高度动态性的场景下,才应该考虑使用反射。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询