在 Java 中使用反射實例化內部類
1. 概述
在本教程中,我們將討論使用 Reflection API 在 Java 中實例化內部類別或巢狀類別。
反射API在需要讀取Java類別結構並動態實例化類別的場景中尤其重要。特定場景包括掃描註解、使用 bean 名稱尋找並實例化 Java bean 等等。一些流行的庫(例如 Spring 和 Hibernate)以及程式碼分析工具廣泛使用它。
與普通類別相比,實例化內部類別帶來了挑戰。讓我們探索更多。
2.內部類別編譯
要在內部類別上使用 Java Reflection 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
類別有兩個內部類別: Address
和Builder
。 Address
類別是非靜態的,因為在現實世界中,地址主要與人的實例相關聯。但是, Builder
是靜態的,因為需要它來建立Person
的實例。因此,在我們實例化Person
類別之前它必須存在。
編譯器為內部類別建立單獨的類別文件,而不是將它們嵌入到外部類別中。在本例中,我們看到編譯器總共創建了三個類別:
編譯器產生了Person
類,有趣的是它還創建了兩個名為Person$Address
和Person$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)](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Class.html#forName(java.lang.String))
方法實例化任何普通類別類似:
@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)
。
最後,我們使用personObj
和zip 751003
參數呼叫建構函式上的newInstance()
方法來取得personAddressObj
。
我們也看到方法personAddressClass.getDeclaredConstructor(String.class)
因為缺少第一個參數this$0
而拋出NoSuchMethodException
。
5. 結論
在本文中,我們討論了用於實例化靜態和非靜態內部類別的 Java Reflection API。我們發現編譯器將內部類別視為外部類別,而不是外部類別中的嵌入類別。
此外,非靜態內部類別的建構子預設將外部類別物件作為第一個參數。但是,我們可以像任何普通類別一樣實例化靜態類別。
與往常一樣,所使用的程式碼可以 在 GitHub 上找到。