1. 概述

在这个教程中,我们将讨论如何使用Java反射API(Java Reflection API)来实例化内部类或嵌套类。反射API在需要动态读取Java类结构并创建实例的场景中尤其重要,例如扫描注解、根据bean名称查找和实例化Java Bean等。Spring和Hibernate等流行库以及代码分析工具广泛使用它。

与普通类相比,实例化内部类会带来一些挑战。让我们深入探讨。

2. 内部类编译

要对内部类使用Java反射API,我们需要理解编译器如何处理它们。以一个示例开始,定义一个名为Person的类,我们将用它演示内部类的实例化:

public class Person {
    String name;
    Address address;

    public Person() {
    }

    public class Address {
        String zip;

        public Address(String zip) {
            this.zip = zip;
        }
    }

    public static class Builder {
    }
}

Person类有两个内部类:AddressBuilderAddress类是非静态的,因为在现实世界中,地址通常与个人实例相关联。然而,Builder是静态的,因为它用于创建Person的实例,所以它必须在实例化Person之前存在。

编译器为内部类创建单独的类文件,而不是将它们嵌入到外部类中。 在此情况下,我们看到总共创建了三个类:

编译器生成了Person类,有趣的是,它还为两个内部类生成了名为Person$AddressPerson$Builder的类。

下一步是了解内部类的构造函数:

@Test
void givenInnerClass_whenUseReflection_thenShowConstructors() {
    final String personBuilderClassName = "com.baeldung.reflection.innerclass.Person$Builder";
    final String personAddressClassName = "com.baeldung.reflection.innerclass.Person$Address";
    assertDoesNotThrow(() -> logConstructors(Class.forName(personAddressClassName)));
    assertDoesNotThrow(() -> logConstructors(Class.forName(personBuilderClassName)));
}

static void logConstructors(Class<?> clazz) {
    Arrays.stream(clazz.getDeclaredConstructors())
      .map(c -> formatConstructorSignature(c))
      .forEach(logger::info);
}

static String formatConstructorSignature(Constructor<?> constructor) {
    String params = Arrays.stream(constructor.getParameters())
      .map(parameter -> parameter.getType().getSimpleName() + " " + parameter.getName())
      .collect(Collectors.joining(", "));
    return constructor.getName() + "(" + params + ")";
}

Class.forName()方法接受内部类的完全限定名并返回Class对象。然后,我们可以使用这个Class对象获取构造函数的详细信息,如通过logConstructors()方法:

com.baeldung.reflection.innerclass.Person$Address(Person this$0, String zip)
com.baeldung.reflection.innerclass.Person$Builder()

令人惊讶的是,非静态的Person$Address类的构造函数中,编译器注入了一个名为this$0的引用,它持有对包含的Person类的引用作为第一个参数。而静态的Person$Builder类的构造函数中没有对外部类的引用。

在实例化内部类时,我们将记住Java编译器的这种行为。

3. 实例化静态内部类

实例化静态内部类的过程几乎与实例化任何普通类相似,只需使用Class.forName(String className)方法:

@Test
void givenStaticInnerClass_whenUseReflection_thenInstantiate()
    throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
      InstantiationException, IllegalAccessException {
    final String personBuilderClassName = "com.baeldung.reflection.innerclass.Person$Builder";
    Class<Person.Builder> personBuilderClass = (Class<Person.Builder>) Class.forName(personBuilderClassName);
    Person.Builder personBuilderObj = personBuilderClass.getDeclaredConstructor().newInstance();
    assertTrue(personBuilderObj instanceof Person.Builder);
}

我们传递了内部类的完全限定名"com.baeldung.reflection.innerclass.Person$Builder"Class.forName()。然后,我们在Person.Builder类的构造器上调用newInstance()方法,得到personBuilderObj

4. 实例化非静态内部类

正如我们之前所见,Java编译器在非静态内部类的构造函数中默认将外部类的对象作为第一个参数。

有了这些知识,让我们尝试实例化Person.Address类:

@Test
void givenNonStaticInnerClass_whenUseReflection_thenInstantiate()
    throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
      InstantiationException, IllegalAccessException {
    final String personClassName = "com.baeldung.reflection.innerclass.Person";
    final String personAddressClassName = "com.baeldung.reflection.innerclass.Person$Address";

    Class<Person> personClass = (Class<Person>) Class.forName(personClassName);
    Person personObj = personClass.getConstructor().newInstance();

    Class<Person.Address> personAddressClass = (Class<Person.Address>) Class.forName(personAddressClassName);

    assertThrows(NoSuchMethodException.class, () -> personAddressClass.getDeclaredConstructor(String.class));
    
    Constructor<Person.Address> constructorOfPersonAddress = personAddressClass.getDeclaredConstructor(Person.class, String.class);
    Person.Address personAddressObj = constructorOfPersonAddress.newInstance(personObj, "751003");
    assertTrue(personAddressObj instanceof Person.Address);
}

首先,我们创建了Person对象。然后,我们传递了内部类的完全限定名"com.baeldung.reflection.innerclass.Person$Address"Class.forName()。接下来,我们从personAddressClass中获取了构造函数Address(Person this$0, String zip)

最后,我们在构造器上调用newInstance()方法,传入personObjzip 751003参数,得到personAddressObj

我们还会发现,personAddressClass.getDeclaredConstructor(String.class)方法抛出NoSuchMethodException,因为缺少第一个参数this$0

5. 总结

在这篇文章中,我们讨论了使用Java反射API实例化静态和非静态内部类的方法。我们发现,编译器将内部类视为外部类,而不是外部类中的嵌入类。

此外,非静态内部类的构造函数默认接受一个外部类对象作为第一个参数。然而,我们可以像普通类一样实例化静态类。

如往常一样,使用的代码可以在GitHub上找到。


« 上一篇: Java Dates