1. 概述

本教程将深入探讨 Java 反射机制,它允许我们在运行时检查和修改类、接口、字段和方法的属性。当我们在编译时无法确定这些组件的具体名称时,反射机制就显得尤为实用。

此外,通过反射我们还能:

  • 实例化新对象
  • 调用方法
  • 获取或设置字段值

2. 项目准备

使用 Java 反射无需引入任何特殊 JAR 包,也无需特殊配置或 Maven 依赖。JDK 自带了一组专门用于此目的的类,它们被打包在 java.lang.reflect 包中。

只需在代码中导入以下内容即可:

import java.lang.reflect.*;

要获取实例的类、方法和字段信息,我们调用 getClass 方法,它会返回对象的运行时类表示。返回的 Class 对象提供了访问类信息的方法。

3. 简单示例

我们先通过一个基础示例了解反射:在运行时检查简单 Java 对象的字段。

创建一个只有 nameage 字段的 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 获取。


原始标题:Guide to Java Reflection

« 上一篇: Java Web Weekly 141
» 下一篇: Java周报,142