使用 Class.newInstance() 創建對象的示例
想像一下,您被分配使用反射創建一個對象。我們開始吧?
我們將從編寫要實例化的類的代碼開始:
public class Employee {
private String name;
private String lastName;
private int age;
{
age = -1;
name = "Rob";
surname = "Stark";
}
public Employee(String name, String surname, int age) {
this.name = name;
this.surname = surname;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return lastName;
}
public void setSurname(String surname) {
this.surname = surname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", surname='" + surname + '\'' +
", age=" + age +
'}';
}
}
這將是我們的類——有幾個字段、一個帶參數的構造函數、getter 和 setter、一個toString()方法和一個初始化塊。現在讓我們繼續第二部分:使用反射創建對象。我們要看的第一種方法將使用Class.newInstance()。
public class Main {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Employee employee = Employee.class.newInstance();
System.out.println("age is " + employee.getAge());
}
}
出色的!讓我們運行我們的代碼並觀察要顯示的年齡。但是我們收到有關缺少默認構造函數的錯誤。事實證明,這個方法只能讓我們得到一個使用默認構造函數創建的對象。讓我們為我們的類添加一個默認構造函數並再次測試代碼。
錯誤信息:

新構造函數的代碼
public Employee() { }
添加構造函數後,輸出如下:
偉大的!我們弄清楚了這種方法是如何工作的。現在讓我們來看看引擎蓋下。打開文檔,我們看到我們的方法已經被棄用了:

它還可以拋出InstantiationException和IllegalAccessException。因此,文檔建議我們使用另一種創建對象的方法,即Constructor.newInstance()。下面詳細分析一下Constructor類是如何工作的。
getConstructors 和 getDeclaredConstructors 方法
要使用Constructor類,我們首先需要獲取一個實例。為此,我們有兩種方法:getConstructors和getDeclaredConstructors。
第一個返回公共構造函數的數組,第二個返回所有類構造函數的數組。
讓我們給我們的類一些隱私,或者更確切地說,讓我們創建一些私有構造函數來幫助演示這些方法是如何工作的。
讓我們添加一些私有構造函數:
private Employee(String name, String surname) {
this.name = name;
this.lastName = lastName;
}
查看代碼,請注意其中一個構造函數是私有的:

讓我們測試一下我們的方法:
public class Main {
public static void main(String[] args) {
Class employeeClass = Employee.class;
System.out.println("getConstructors:");
printAllConstructors(employeeClass);
System.out.println("\n" +"getDeclaredConstructors:");
printDeclaredConstructors(employeeClass);
}
static void printDeclaredConstructors(Class<?> c){
for (Constructor<?> constructor : c.getDeclaredConstructors()) {
System.out.println(constructor);
}
}
static void printAllConstructors(Class<?> c){
for (Constructor<?> constructor : c.getConstructors()) {
System.out.println(constructor);
}
}
}
我們得到這個結果:
公共 com.codegym.Employee(java.lang.String,java.lang.String,int)
public.com.codegym.Employee()
getDeclaredConstructors:
private com.codegym.Employee(java.lang.String,java.lang .String)
public com.codegym.Employee(java.lang.String,java.lang.String,int)
public com.codegym.Employee()
好的,這就是我們訪問Constructor對象的方式。現在我們可以談談它能做什麼。
java.lang.reflect.Constructor 類及其最重要的方法
讓我們來看看最重要的方法及其工作原理:
方法 | 描述 |
---|---|
獲取名稱() | 以字符串形式返回此構造函數的名稱。 |
getModifiers() | 返回編碼為數字的 Java 訪問修飾符。 |
getExceptionTypes() 方法 | 返回一個 Class 對像數組,這些對象表示構造函數聲明的異常類型。 |
獲取參數() | 返回表示所有參數的參數對像數組。如果構造函數沒有參數,則返回長度為 0 的數組。 |
獲取參數類型() | 返回一個 Class 對像數組,這些對象按聲明順序表示形式參數類型。 |
getGenericParameterTypes() | 返回一個 Type 對像數組,這些對象按聲明順序表示形式參數類型。 |
getName() 和 getModifiers()
讓我們將數組包裝在一個列表中,以便於使用。我們還將編寫getName和getModifiers方法:
static List<Constructor<?>> getAllConstructors(Class<?> c) {
return new ArrayList<>(Arrays.asList(c.getDeclaredConstructors()));
}
static List<String> getConstructorNames(List<Constructor<?>> constructors) {
List<String> result = new ArrayList<>();
for (Constructor<?> constructor : constructors) {
result.add(constructor.toString());
}
return result;
}
static List<String> getConstructorModifiers(List<Constructor<?>> constructors) {
List<String> result = new ArrayList<>();
for (Constructor<?> constructor : constructors) {
result.add(Modifier.toString(constructor.getModifiers()));
}
return result;
}
還有我們的主要方法,我們將在其中調用所有內容:
public static void main(String[] args) {
Class employeeClass = Employee.class;
var constructors = getAllConstructors(employeeClass);
var constructorNames = getConstructorNames(constructors);
var constructorModifiers = getConstructorModifiers(constructors);
System.out.println("Employee class:");
System.out.println("Constructors :");
System.out.println(constructorNames);
System.out.println("Modifiers :");
System.out.println(constructorModifiers);
}
現在我們看到了我們想要的所有信息:
構造函數:
[private com.codegym.Employee(java.lang.String), public
com.codegym.Employee(java.lang.String,java.lang.String,int), public com.codegym.Employee() ]
修飾符:
[私有,公共,公共]
getExceptionTypes() 方法
這個方法讓我們得到一個構造函數可能拋出的異常數組。讓我們修改一個構造函數並編寫一個新方法。
在這裡,我們稍微更改當前的構造函數:
private Employee(String name, String surname) throws Exception {
this.name = name;
this.lastName = lastName;
}
這裡我們有一個獲取異常類型並將其添加到main 的方法:
static List<Class<?>> getConstructorExceptionTypes(Constructor<?> c) {
return new ArrayList<>(Arrays.asList(c.getExceptionTypes()));
}
var constructorExceptionTypes = getConstructorExceptionTypes(constructors.get(0));
System.out.println("Exception types :");
System.out.println(constructorExceptionTypes);
上面,我們訪問了列表中的第一個構造函數。稍後我們將討論如何獲得特定的構造函數。
並查看我們添加throws Exception後的輸出:
[class java.lang.Exception]
在添加異常之前:
[]
一切都很棒,但是我們如何查看我們的構造函數需要哪些參數呢?讓我們也弄清楚這一點。
getParameters() & getParameterTypes() & getGenericParameterTypes()
讓我們重新開始完善我們的私有構造函數。現在它看起來像這樣:
private Employee(String name, String surname, List<String> list) {
this.name = name;
this.lastName = lastName;
}
我們還有另外三個方法:getParameters用於獲取參數的順序及其類型,getParameterTypes用於獲取參數類型,getGenericParameterTypes用於獲取泛型包裝的類型。
static List<Parameter> getConstructorParameters(Constructor<?> c) {
return new ArrayList<>(Arrays.asList(c.getParameters()));
}
static List<Class<?>> getConstructorParameterTypes(Constructor<?> c) {
return new ArrayList<>(Arrays.asList(c.getParameterTypes()));
}
static List<Type> getConstructorParametersGenerics(Constructor<?> c) {
return new ArrayList<>(Arrays.asList(c.getGenericParameterTypes()));
}
我們向已經不小的main方法添加更多信息:
var constructorParameterTypes = getConstructorParameterTypes(constructors.get(0));
var constructorParameters = getConstructorParameters(constructors.get(0));
var constructorParametersGenerics = getConstructorParametersGenerics(constructors.get(0));
System.out.println("Constructor parameters :");
System.out.println(constructorParameters);
System.out.println("Parameter types :");
System.out.println(constructorParameterTypes);
System.out.println("Constructor parameter types :");
System.out.println(constructorParametersGenerics);
查看輸出,我們可以看到有關構造函數參數的非常詳細的信息:
[java.lang.String arg0, java.lang.String arg1, java.util.List<java.lang.String> arg2]
參數類型:
[class java.lang.String, class java.lang.String, interface java.util.List]
構造函數參數類型:
[class java.lang.String, class java.lang.String, java.util.List<java.lang.String>]
這清楚地展示了每種方法之間的區別。我們看到我們有單獨的選項來獲取有關參數類型、包裝類型和所有一般信息的信息。極好的!現在我們已經熟悉了Constructor類,我們可以回到本文的主題 — 創建對象。
使用 Constructor.newInstance() 創建對象
第二種創建對象的方法是在構造函數上調用newInstance方法。讓我們看一個工作示例,看看我們如何獲得特定的構造函數。
如果您想獲得單個構造函數,您應該使用getConstructor方法(不要與getConstructors混淆,後者返回所有構造函數的數組)。getConstructor方法返回默認構造函數。
public static void main(String[] args) throws NoSuchMethodException {
Class employeeClass = Employee.class;
Constructor<?> employeeConstructor = employeeClass.getConstructor();
System.out.println(employeeConstructor);
}
而如果我們想得到具體的構造函數,就需要將構造函數的參數類型傳遞給這個方法。
不要忘記我們只能使用getDeclaredConstructor方法獲取我們的私有構造函數。
Constructor<?> employeeConstructor2 = employeeClass.getDeclaredConstructor(String.class, String.class, List.class);
System.out.println(employeeConstructor2);
這就是我們如何獲得特定的構造函數。現在讓我們嘗試使用私有和公共構造函數創建對象。
公共構造函數:
Class employeeClass = Employee.class;
Constructor<?> employeeConstructor = employeeClass.getConstructor(String.class, String.class, int.class);
System.out.println(employeeConstructor);
Employee newInstance = (Employee) employeeConstructor.newInstance("Rob", "Stark", 10);
System.out.println(newInstance);
結果是一個我們可以使用的對象:
Employee{name='Rob' surname='Stark', age=10}
一切都很好!現在我們將嘗試使用私有構造函數:
Constructor<?> declaredConstructor = employeeClass.getDeclaredConstructor(String.class, String.class, List.class);
System.out.println(declaredConstructor);
Employee newInstance2 = (Employee) declaredConstructor.newInstance("Rob", "Stark", new ArrayList<>());
System.out.printf(newInstance2.toString());
結果是關於構造函數隱私的錯誤:

Java 無法使用此構造函數創建對象,但實際上我們可以在main方法中做一些神奇的事情。我們可以訪問構造函數的訪問級別,從而可以創建我們類的對象:
declaredConstructor.setAccessible(true);
創建對象的結果
Employee{name='Rob', surname='Stark', age=-1}
我們沒有在構造函數中設置年齡,因此它與初始化時保持不變。
精彩,總結一下!
使用 Constructor.newInstance() 創建對象的好處
這兩種方法具有相同的名稱,但它們之間存在差異:
類.newInstance() | 構造函數.newInstance() |
---|---|
只能調用無參數構造函數。 | 無論參數的數量如何,都可以調用任何構造函數。 |
要求構造函數可見。 | 在某些情況下也可以調用私有構造函數。 |
拋出構造函數聲明的任何異常(已檢查或未檢查)。 | 始終使用InvocationTargetException包裝拋出的異常。 |
由於這些原因,Constructor.newInstance()優於Class.newInstance(),並且是各種框架和 API 使用的方法,例如 Spring、Guava、Zookeeper、Jackson、Servlet 等。
GO TO FULL VERSION