1. 获取字段信息
getFields() 与 getDeclaredFields() 有何区别
在 Java 中,每个类都有字段(fields)——即在类内部声明的变量。借助反射,我们可以在运行时获知它们的名称、类型、访问修饰符(public/private),甚至访问它们。主要入口是一个 Class<?> 对象。
用于处理字段的 Class 类的主要方法:
| 方法 | 返回什么 |
|---|---|
|
该类、其父类和接口中的所有 public 字段数组 |
|
所有 在该类中声明的字段数组 |
类比:getFields() 像只参观展厅(public),而 getDeclaredFields() 还会带你走进技术用房(private、protected、package-private)。
示例:输出类的所有字段
假设我们有如下类:
public class Person {
public String name;
private int age;
protected String email;
}
获取字段信息:
import java.lang.reflect.Field;
Class<?> clazz = Person.class;
// 所有 public 字段(包括继承而来的)
System.out.println("Public fields:");
for (Field field : clazz.getFields()) {
System.out.println(field.getName() + " : " + field.getType().getSimpleName());
}
// 所有声明的字段(包括 private,仅本类)
System.out.println("\nDeclared fields:");
for (Field field : clazz.getDeclaredFields()) {
System.out.println(field.getName() + " : " + field.getType().getSimpleName());
}
结果:
Public fields:
name : String
Declared fields:
name : String
age : int
email : String
如何获取字段的修饰符?
每个字段(Field)都有修饰符(public/private/protected、static、final 等)。可以通过 getModifiers() 获取,并使用 Modifier 转成字符串:
import java.lang.reflect.Modifier;
for (Field field : clazz.getDeclaredFields()) {
int mods = field.getModifiers();
System.out.println(field.getName() + " : " + Modifier.toString(mods));
}
2. 获取方法信息
getMethods() 与 getDeclaredMethods()
方法是对象可以执行的操作。借助反射可以获知一个类有哪些方法、它们的参数与返回类型、修饰符以及注解。
| 方法 | 返回什么 |
|---|---|
|
该类及其父类的所有 public 方法(包括 Object) |
|
所有 在该类中声明的方法(包括 private) |
示例:输出类的所有方法
import java.lang.reflect.Method;
System.out.println("Public methods:");
for (Method method : clazz.getMethods()) {
System.out.println(method.getName());
}
System.out.println("\nDeclared methods:");
for (Method method : clazz.getDeclaredMethods()) {
System.out.println(method.getName());
}
结果(对于 Person):
Public methods:
getClass
hashCode
equals
toString
notify
notifyAll
wait
wait
wait
Declared methods:
(none, if Person has no declared methods)
给 Person 添加一个方法:
public class Person {
public String name;
private int age;
protected String email;
public void sayHello() {
System.out.println("Hi!");
}
}
现在 getDeclaredMethods() 也会显示它。
如何获取方法的参数和返回类型?
for (Method method : clazz.getDeclaredMethods()) {
System.out.print(Modifier.toString(method.getModifiers()) + " ");
System.out.print(method.getReturnType().getSimpleName() + " ");
System.out.print(method.getName() + "(");
Class<?>[] params = method.getParameterTypes();
for (int i = 0; i < params.length; i++) {
System.out.print(params[i].getSimpleName());
if (i < params.length - 1) System.out.print(", ");
}
System.out.println(");");
}
输出:
public void sayHello();
3. 获取构造器信息
构造器是用于创建对象的特殊方法。
| 方法 | 返回什么 |
|---|---|
|
所有 public 构造器 |
|
所有 在类中声明的构造器 |
示例:输出类的所有构造器
import java.lang.reflect.Constructor;
System.out.println("Constructors:");
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
System.out.print(clazz.getSimpleName() + "(");
Class<?>[] params = constructor.getParameterTypes();
for (int i = 0; i < params.length; i++) {
System.out.print(params[i].getSimpleName());
if (i < params.length - 1) System.out.print(", ");
}
System.out.println(");");
}
如果类只声明了默认构造器,它也会被输出。
4. 注解:如何知道类、方法或字段上标注了什么
注解是可以添加到类、方法、字段和参数上的特殊标记。通过反射可以获知元素上有哪些注解。
获取注解
- 对于类:clazz.getAnnotations()
- 对于方法:method.getAnnotations()
- 对于字段:field.getAnnotations()
示例:检查是否存在注解 @Deprecated
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Deprecated.class)) {
System.out.println(method.getName() + " is @Deprecated");
}
}
示例:输出类的所有注解
for (var annotation : clazz.getAnnotations()) {
System.out.println(annotation);
}
5. 实战:用于分析类结构的迷你程序
我们把这些放在一起,编写一个小工具,根据类名输出它的结构:字段、方法、构造器和注解。
代码示例:ClassInspector
import java.lang.reflect.*;
import java.util.Scanner;
public class ClassInspector {
public static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入类的全限定名(例如,java.util.ArrayList):");
String className = scanner.nextLine();
Class<?> clazz = Class.forName(className);
System.out.println("\n== 类: " + clazz.getName() + " ==");
// 类的注解
System.out.println("注解:");
for (Annotation annotation : clazz.getAnnotations()) {
System.out.println(" " + annotation);
}
// 字段
System.out.println("\n字段:");
for (Field field : clazz.getDeclaredFields()) {
System.out.println(" " + Modifier.toString(field.getModifiers())
+ " " + field.getType().getSimpleName()
+ " " + field.getName());
}
// 方法
System.out.println("\n方法:");
for (Method method : clazz.getDeclaredMethods()) {
System.out.print(" " + Modifier.toString(method.getModifiers())
+ " " + method.getReturnType().getSimpleName()
+ " " + method.getName() + "(");
Class<?>[] params = method.getParameterTypes();
for (int i = 0; i < params.length; i++) {
System.out.print(params[i].getSimpleName());
if (i < params.length - 1) System.out.print(", ");
}
System.out.println(");");
}
// 构造器
System.out.println("\n构造器:");
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
System.out.print(" " + Modifier.toString(constructor.getModifiers())
+ " " + clazz.getSimpleName() + "(");
Class<?>[] params = constructor.getParameterTypes();
for (int i = 0; i < params.length; i++) {
System.out.print(params[i].getSimpleName());
if (i < params.length - 1) System.out.print(", ");
}
System.out.println(");");
}
}
}
它如何工作?
- 用户输入类的全限定名(例如,"java.util.ArrayList" 或你自己的类)。
- 程序动态加载该类,并输出它的注解、字段、方法和构造器。
- 试试 JDK 的标准类——你会看到内部隐藏了多少有趣的内容!
6. 一些有用的细节与可视化
可视化示意:通过反射可以了解到类的哪些信息
graph TD
A[Class<?>] --> B["getFields/getDeclaredFields"]
A --> C[getMethods/getDeclaredMethods]
A --> D[getConstructors/getDeclaredConstructors]
A --> E[getAnnotations]
B --> F[Field: 类型、名称、修饰符]
C --> G[Method: 返回类型、参数]
D --> H[Constructor: 参数]
E --> I["Annotation[]"]
表格:到哪里查找信息
| 想了解什么 | 如何通过反射获取 |
|---|---|
| 所有 public 字段 | |
| 所有已声明的字段 | |
| 所有 public 方法 | |
| 所有已声明的方法 | |
| 所有 public 构造器 | |
| 所有已声明的构造器 | |
| 类的注解 | |
| 方法/字段的注解 | |
| 修饰符 | |
7. 使用反射的常见错误
错误 1:混淆 getFields() 与 getDeclaredFields()。
如果你要查找 private 字段,请使用 getDeclaredFields(),而不是 getFields()。前者返回类中声明的所有字段,后者只返回 public(并且包含继承而来的字段!)。
错误 2:未处理受检异常。
许多反射方法会抛出异常(例如,ClassNotFoundException 或 SecurityException)。别忘了处理它们,或在方法签名中声明。
错误 3:未考虑访问修饰符。
访问 private 字段和方法前需要先调用 setAccessible(true),否则会抛出 IllegalAccessException。示例:
Field field = clazz.getDeclaredField("age");
field.setAccessible(true); // 打开对 private 字段的访问
int value = (int) field.get(person);
错误 4:期望只看到自己定义的方法/字段。
getMethods() 和 getFields() 返回的不仅是当前类的 public 成员,还包括其所有父类的成员(包括 Object)。如果你只期望看到自己写的内容,这可能会让你惊讶。
错误 5:未正确检查注解是否存在。
请使用 isAnnotationPresent() 检查特定注解,而不是无谓地手动遍历注解数组。
GO TO FULL VERSION