Java 反射 (Reflection)
反射(Reflection)是 Java 提供的一种强大的高级特性,它允许正在运行的 Java 程序检查或“反思”自身,并能在运行时操作类、接口、字段和方法。
什么是反射?
反射机制允许程序在运行时:
- 获取任意一个类的完整结构(包括它的父类、接口、构造方法、方法、字段等)。
- 在运行时创建任意一个类的实例。
- 在运行时调用任意一个对象的方法。
- 在运行时获取或设置任意一个对象的字段值,即使是
private成员。
反射的核心是 java.lang.Class 类和 java.lang.reflect 包中的一系列类,如 Constructor, Method, Field 等。
Class 对象
每个加载到 JVM 中的类,都会在内存中创建一个对应的 Class 对象。这个 Class 对象包含了该类的所有信息,是反射的入口点。
获取 Class 对象的三种方式
通过类名
.class:最直接、最安全的方式,在编译时就会受到检查。javaClass<String> stringClass = String.class;通过对象的
getClass()方法:如果已经有了一个对象实例,可以调用它的getClass()方法。javaString s = "Hello"; Class<?> sClass = s.getClass();通过类的完全限定名
Class.forName():这种方式最灵活,可以在运行时根据一个字符串动态加载类。javatry { Class<?> driverClass = Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); }
使用反射检查类信息
一旦获得了 Class 对象,就可以用它来探索类的内部结构。
java
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionInfoExample {
public static void main(String[] args) {
Class<String> clazz = String.class;
// 获取类名
System.out.println("类名: " + clazz.getName());
System.out.println("简单类名: " + clazz.getSimpleName());
// 获取所有 public 字段
System.out.println("\nPublic Fields:");
for (Field field : clazz.getFields()) {
System.out.println(" - " + field.getName());
}
// 获取所有声明的字段(包括 private)
System.out.println("\nDeclared Fields:");
for (Field field : clazz.getDeclaredFields()) {
System.out.println(" - " + field.getName());
}
// 获取所有 public 方法
System.out.println("\nPublic Methods:");
for (Method method : clazz.getMethods()) {
System.out.println(" - " + method.getName());
}
}
}使用反射创建实例和调用方法
反射最强大的功能是在运行时动态地创建对象和执行操作。
创建实例
- 如果类有公共的无参构造方法,可以直接调用
clazz.newInstance()(已过时) 或clazz.getDeclaredConstructor().newInstance()。 - 如果需要使用带参数的构造方法,需要先获取
Constructor对象。
调用方法
- 通过
getMethod()或getDeclaredMethod()获取Method对象。 - 调用
method.invoke(object, args...)来执行方法。
java
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class ReflectionActionExample {
public static void main(String[] args) throws Exception {
// 1. 获取 Class 对象
Class<?> clazz = String.class;
// 2. 创建实例
// 获取 String(char[]) 构造方法
Constructor<?> constructor = clazz.getConstructor(char[].class);
char[] value = {'H', 'e', 'l', 'l', 'o'};
Object instance = constructor.newInstance(value);
System.out.println("创建的实例: " + instance); // "Hello"
// 3. 调用方法
// 获取 toUpperCase() 方法
Method method = clazz.getMethod("toUpperCase");
// 调用方法,对于实例方法,第一个参数是实例对象
Object result = method.invoke(instance);
System.out.println("调用 toUpperCase() 结果: " + result); // "HELLO"
}
}访问私有成员
反射甚至可以突破 private 访问限制。这在单元测试或某些框架中很有用,但应谨慎使用,因为它破坏了封装性。
- 使用
getDeclaredField()或getDeclaredMethod()获取私有成员。 - 在访问前,必须调用
setAccessible(true)来禁用访问安全检查。
java
// 假设有一个 Person 类
// public class Person {
// private String name;
// public Person(String name) { this.name = name; }
// }
// Person person = new Person("Alice");
// Field nameField = person.getClass().getDeclaredField("name");
// nameField.setAccessible(true); // 禁用访问检查
// String nameValue = (String) nameField.get(person);
// System.out.println(nameValue); // 输出 "Alice"反射的优缺点
优点:
- 灵活性和动态性:使程序能够在运行时加载、检查和使用编译时未知的类。
- 框架开发:是许多现代框架(如 Spring, Hibernate, JUnit)的基石,用于实现依赖注入、ORM 等功能。
缺点:
- 性能开销:反射操作比直接代码调用要慢得多。
- 安全性问题:可以绕过访问控制,破坏封装性。
- 代码可读性差:反射代码通常更复杂,更难理解和调试。