1. 概述
本教程将深入探讨 Java 反射机制,它允许我们在运行时检查和修改类、接口、字段和方法的属性。当我们在编译时无法确定这些组件的具体名称时,反射机制就显得尤为实用。
此外,通过反射我们还能:
- 实例化新对象
- 调用方法
- 获取或设置字段值
2. 项目准备
使用 Java 反射无需引入任何特殊 JAR 包,也无需特殊配置或 Maven 依赖。JDK 自带了一组专门用于此目的的类,它们被打包在 java.lang.reflect
包中。
只需在代码中导入以下内容即可:
import java.lang.reflect.*;
要获取实例的类、方法和字段信息,我们调用 getClass
方法,它会返回对象的运行时类表示。返回的 Class
对象提供了访问类信息的方法。
3. 简单示例
我们先通过一个基础示例了解反射:在运行时检查简单 Java 对象的字段。
创建一个只有 name
和 age
字段的 Person
类:
public class Person {
private String name;
private int age;
}
现在使用反射获取该类的所有字段名:
@Test
public void givenObject_whenGetsFieldNamesAtRuntime_thenCorrect() {
Object person = new Person();
Field[] fields = person.getClass().getDeclaredFields();
List<String> actualFieldNames = getFieldNames(fields);
assertTrue(Arrays.asList("name", "age")
.containsAll(actualFieldNames));
}
这个测试展示了即使对象引用是父类型,我们仍能获取其 Field
对象数组。上述示例中我们只关注字段名,但反射能做的远不止这些,后续章节会展示更多用法。
注意我们使用了辅助方法提取字段名:
private static List<String> getFieldNames(Field[] fields) {
List<String> fieldNames = new ArrayList<>();
for (Field field : fields)
fieldNames.add(field.getName());
return fieldNames;
}
4. Java 反射应用场景
深入探讨反射特性前,先了解其常见用例。Java 反射功能强大,在多种场景下都很有用:
✅ 数据库映射:当数据库表有命名规范时(如前缀 tbl_
),可将 Java 对象名(如 Student
)映射到表名(如 tbl_student_data
)。通过反射获取对象名和字段名,实现通用 CRUD 操作。
✅ 框架开发:Spring 等框架大量使用反射实现依赖注入和动态代理。
✅ 调试与测试:运行时检查对象状态,动态调用私有方法进行测试。
⚠️ 性能考量:反射操作比直接调用慢,应避免在性能关键路径过度使用。
5. 检查 Java 类
本节探讨反射 API 的核心组件:Class
对象。它提供访问任何对象内部细节的能力,包括:
- 类名
- 修饰符
- 字段
- 方法
- 实现的接口
5.1 准备工作
为展示反射 API 的多样性,创建以下类结构:
public interface Eating {
String eats();
}
public abstract class Animal implements Eating {
public static String CATEGORY = "domestic";
private String name;
protected abstract String getSound();
// 构造函数、标准 getter/setter 省略
}
public interface Locomotion {
String getLocomotion();
}
public class Goat extends Animal implements Locomotion {
@Override
protected String getSound() {
return "bleat";
}
@Override
public String getLocomotion() {
return "walks";
}
@Override
public String eats() {
return "grass";
}
// 构造函数省略
}
后续将使用反射检查这些类的特性。
5.2 类名获取
从 Class
对象获取类名:
@Test
public void givenObject_whenGetsClassName_thenCorrect() {
Object goat = new Goat("goat");
Class<?> clazz = goat.getClass();
assertEquals("Goat", clazz.getSimpleName());
assertEquals("com.baeldung.reflection.Goat", clazz.getName());
assertEquals("com.baeldung.reflection.Goat", clazz.getCanonicalName());
}
getSimpleName()
:返回类声明中的基础名称getName()
和getCanonicalName()
:返回包含包声明的完整类名
通过全限定类名创建 Class
对象:
@Test
public void givenClassName_whenCreatesObject_thenCorrect(){
Class<?> clazz = Class.forName("com.baeldung.reflection.Goat");
assertEquals("Goat", clazz.getSimpleName());
assertEquals("com.baeldung.reflection.Goat", clazz.getName());
assertEquals("com.baeldung.reflection.Goat", clazz.getCanonicalName());
}
⚠️ 传递给 forName
的名称必须包含包信息,否则会抛出 ClassNotFoundException
。
5.3 类修饰符检查
通过 getModifiers()
获取修饰符整数,使用 java.lang.reflect.Modifier
的静态方法分析修饰符:
@Test
public void givenClass_whenRecognisesModifiers_thenCorrect() {
Class<?> goatClass = Class.forName("com.baeldung.reflection.Goat");
Class<?> animalClass = Class.forName("com.baeldung.reflection.Animal");
int goatMods = goatClass.getModifiers();
int animalMods = animalClass.getModifiers();
assertTrue(Modifier.isPublic(goatMods));
assertTrue(Modifier.isAbstract(animalMods));
assertTrue(Modifier.isPublic(animalClasses));
}
5.4 包信息获取
通过 getPackage()
获取包信息:
@Test
public void givenClass_whenGetsPackageInfo_thenCorrect() {
Goat goat = new Goat("goat");
Class<?> goatClass = goat.getClass();
Package pkg = goatClass.getPackage();
assertEquals("com.baeldung.reflection", pkg.getName());
}
5.5 超类获取
获取类的超类:
@Test
public void givenClass_whenGetsSuperClass_thenCorrect() {
Goat goat = new Goat("goat");
String str = "any string";
Class<?> goatClass = goat.getClass();
Class<?> goatSuperClass = goatClass.getSuperclass();
assertEquals("Animal", goatSuperClass.getSimpleName());
assertEquals("Object", str.getClass().getSuperclass().getSimpleName());
}
5.6 实现接口检查
获取类实现的接口列表:
@Test
public void givenClass_whenGetsImplementedInterfaces_thenCorrect(){
Class<?> goatClass = Class.forName("com.baeldung.reflection.Goat");
Class<?> animalClass = Class.forName("com.baeldung.reflection.Animal");
Class<?>[] goatInterfaces = goatClass.getInterfaces();
Class<?>[] animalInterfaces = animalClass.getInterfaces();
assertEquals(1, goatInterfaces.length);
assertEquals(1, animalInterfaces.length);
assertEquals("Locomotion", goatInterfaces[0].getSimpleName());
assertEquals("Eating", animalInterfaces[0].getSimpleName());
}
⚠️ 注意:只有类通过 implements
关键字显式声明的接口才会出现在返回数组中。继承自超类的接口不会包含在内。
5.7 构造函数、方法和字段检查
检查类的构造函数、方法和字段:
@Test
public void givenClass_whenGetsConstructor_thenCorrect(){
Class<?> goatClass = Class.forName("com.baeldung.reflection.Goat");
Constructor<?>[] constructors = goatClass.getConstructors();
assertEquals(1, constructors.length);
assertEquals("com.baeldung.reflection.Goat", constructors[0].getName());
}
@Test
public void givenClass_whenGetsFields_thenCorrect(){
Class<?> animalClass = Class.forName("com.baeldung.reflection.Animal");
Field[] fields = animalClass.getDeclaredFields();
List<String> actualFields = getFieldNames(fields);
assertEquals(2, actualFields.size());
assertTrue(actualFields.containsAll(Arrays.asList("name", "CATEGORY")));
}
@Test
public void givenClass_whenGetsMethods_thenCorrect(){
Class<?> animalClass = Class.forName("com.baeldung.reflection.Animal");
Method[] methods = animalClass.getDeclaredMethods();
List<String> actualMethods = getMethodNames(methods);
assertEquals(4, actualMethods.size());
assertTrue(actualMethods.containsAll(Arrays.asList("getName",
"setName", "getSound")));
}
辅助方法 getMethodNames
:
private static List<String> getMethodNames(Method[] methods) {
List<String> methodNames = new ArrayList<>();
for (Method method : methods)
methodNames.add(method.getName());
return methodNames;
}
6. 检查构造函数
通过反射可以检查构造函数并在运行时创建对象,这由 java.lang.reflect.Constructor
类实现。
创建 Bird
类(继承 Animal
)展示构造函数操作:
public class Bird extends Animal {
private boolean walks;
public Bird() {
super("bird");
}
public Bird(String name, boolean walks) {
super(name);
setWalks(walks);
}
public Bird(String name) {
super(name);
}
public boolean walks() {
return walks;
}
// 标准 setter 和重写方法省略
}
验证构造函数数量:
@Test
public void givenClass_whenGetsAllConstructors_thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Constructor<?>[] constructors = birdClass.getConstructors();
assertEquals(3, constructors.length);
}
根据参数类型获取特定构造函数:
@Test
public void givenClass_whenGetsEachConstructorByParamTypes_thenCorrect(){
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Constructor<?> cons1 = birdClass.getConstructor();
Constructor<?> cons2 = birdClass.getConstructor(String.class);
Constructor<?> cons3 = birdClass.getConstructor(String.class, boolean.class);
}
通过构造函数实例化对象:
@Test
public void givenClass_whenInstantiatesObjectsAtRuntime_thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Constructor<?> cons1 = birdClass.getConstructor();
Constructor<?> cons2 = birdClass.getConstructor(String.class);
Constructor<?> cons3 = birdClass.getConstructor(String.class,
boolean.class);
Bird bird1 = (Bird) cons1.newInstance();
Bird bird2 = (Bird) cons2.newInstance("Weaver bird");
Bird bird3 = (Bird) cons3.newInstance("dove", true);
assertEquals("bird", bird1.getName());
assertEquals("Weaver bird", bird2.getName());
assertEquals("dove", bird3.getName());
assertFalse(bird1.walks());
assertTrue(bird3.walks());
}
⚠️ Class.newInstance()
方法已自 Java 9 起被废弃,不应在现代项目中使用。
7. 检查字段
本节展示如何在运行时获取和设置字段值。
7.1 获取字段
getFields()
:返回所有可访问的 public 字段(包括继承的)getField(fieldName)
:返回指定名称的 public 字段
@Test
public void givenClass_whenGetsPublicFields_thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Field[] fields = birdClass.getFields();
assertEquals(1, fields.length);
assertEquals("CATEGORY", fields[0].getName());
}
@Test
public void givenClass_whenGetsPublicFieldByName_thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Field field = birdClass.getField("CATEGORY");
assertEquals("CATEGORY", field.getName());
}
getDeclaredFields()
:返回类中声明的所有字段(不包括继承的)getDeclaredField(name)
:返回类中声明的指定字段
@Test
public void givenClass_whenGetsDeclaredFields_thenCorrect(){
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Field[] fields = birdClass.getDeclaredFields();
assertEquals(1, fields.length);
assertEquals("walks", fields[0].getName());
}
@Test
public void givenClass_whenGetsFieldsByName_thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Field field = birdClass.getDeclaredField("walks");
assertEquals("walks", field.getName());
}
7.2 字段类型与值操作
获取字段类型:
@Test
public void givenClassField_whenGetsType_thenCorrect() {
Field field = Class.forName("com.baeldung.reflection.Bird")
.getDeclaredField("walks");
Class<?> fieldClass = field.getType();
assertEquals("boolean", fieldClass.getSimpleName());
}
修改字段值(需先设置可访问):
@Test
public void givenClassField_whenSetsAndGetsValue_thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Bird bird = (Bird) birdClass.getConstructor().newInstance();
Field field = birdClass.getDeclaredField("walks");
field.setAccessible(true);
assertFalse(field.getBoolean(bird));
assertFalse(bird.walks());
field.set(bird, true);
assertTrue(field.getBoolean(bird));
assertTrue(bird.walks());
}
操作静态字段(传入 null
作为实例):
@Test
public void givenClassField_whenGetsAndSetsWithNull_thenCorrect(){
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Field field = birdClass.getField("CATEGORY");
field.setAccessible(true);
assertEquals("domestic", field.get(null));
}
8. 检查方法
通过反射可以在运行时调用方法并传递参数,类似构造函数操作。
8.1 获取方法
getMethods()
:返回所有 public 方法(包括继承的)getDeclaredMethods()
:返回类中声明的所有方法
@Test
public void givenClass_whenGetsAllPublicMethods_thenCorrect(){
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Method[] methods = birdClass.getMethods();
List<String> methodNames = getMethodNames(methods);
assertTrue(methodNames.containsAll(Arrays
.asList("equals", "notifyAll", "hashCode",
"walks", "eats", "toString")));
}
@Test
public void givenClass_whenGetsOnlyDeclaredMethods_thenCorrect(){
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
List<String> actualMethodNames
= getMethodNames(birdClass.getDeclaredMethods());
List<String> expectedMethodNames = Arrays
.asList("setWalks", "walks", "getSound", "eats");
assertEquals(expectedMethodNames.size(), actualMethodNames.size());
assertTrue(expectedMethodNames.containsAll(actualMethodNames));
assertTrue(actualMethodNames.containsAll(expectedMethodNames));
}
根据名称和参数类型获取特定方法:
@Test
public void givenMethodName_whenGetsMethod_thenCorrect() throws Exception {
Bird bird = new Bird();
Method walksMethod = bird.getClass().getDeclaredMethod("walks");
Method setWalksMethod = bird.getClass().getDeclaredMethod("setWalks", boolean.class);
assertTrue(walksMethod.canAccess(bird));
assertTrue(setWalksMethod.canAccess(bird));
}
8.2 调用方法
在运行时调用方法:
@Test
public void givenMethod_whenInvokes_thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Bird bird = (Bird) birdClass.getConstructor().newInstance();
Method setWalksMethod = birdClass.getDeclaredMethod("setWalks", boolean.class);
Method walksMethod = birdClass.getDeclaredMethod("walks");
boolean walks = (boolean) walksMethod.invoke(bird);
assertFalse(walks);
assertFalse(bird.walks());
setWalksMethod.invoke(bird, true);
boolean walks2 = (boolean) walksMethod.invoke(bird);
assertTrue(walks2);
assertTrue(bird.walks());
}
9. 总结
本文全面介绍了 Java 反射 API,展示了如何在编译时未知内部结构的情况下,于运行时检查类、接口、字段和方法。通过反射,我们可以:
- 动态实例化对象
- 检查和修改字段值
- 调用任意方法
- 分析类结构
完整源码和示例可在 GitHub 获取。