使用 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() { }

添加構造函數後,輸出如下:

年齡是 1

偉大的!我們弄清楚了這種方法是如何工作的。現在讓我們來看看引擎蓋下。打開文檔,我們看到我們的方法已經被棄用了

它還可以拋出InstantiationExceptionIllegalAccessException。因此,文檔建議我們使用另一種創建對象的方法,即Constructor.newInstance()。下面詳細分析一下Constructor類是如何工作的。

getConstructors 和 getDeclaredConstructors 方法

要使用Constructor類,我們首先需要獲取一個實例。為此,我們有兩種方法:getConstructorsgetDeclaredConstructors

第一個返回公共構造函數的數組,第二個返回所有類構造函數的數組。

讓我們給我們的類一些隱私,或者更確切地說,讓我們創建一些私有構造函數來幫助演示這些方法是如何工作的。

讓我們添加一些私有構造函數:

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);
	      }
	  }
}

我們得到這個結果:

getConstructors:
公共 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()

讓我們將數組包裝在一個列表中,以便於使用。我們還將編寫getNamegetModifiers方法:

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);
}
公共 com.codegym.Employee()

而如果我們想得到具體的構造函數,就需要將構造函數的參數類型傳遞給這個方法。

不要忘記我們只能使用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);

結果是一個我們可以使用的對象:

public com.codegym.Employee(java.lang.String,java.lang.String,int)
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);

創建對象的結果

私人 com.codegym.Employee(java.lang.String,java.lang.String,java.util.List)
Employee{name='Rob', surname='Stark', age=-1}

我們沒有在構造函數中設置年齡,因此它與初始化時保持不變。

精彩,總結一下!

使用 Constructor.newInstance() 創建對象的好處

這兩種方法具有相同的名稱,但它們之間存在差異:

類.newInstance() 構造函數.newInstance()
只能調用無參數構造函數。 無論參數的數量如何,都可以調用任何構造函數。
要求構造函數可見。 在某些情況下也可以調用私有構造函數。
拋出構造函數聲明的任何異常(已檢查或未檢查)。 始終使用InvocationTargetException包裝拋出的異常。

由於這些原因,Constructor.newInstance()優於Class.newInstance(),並且是各種框架和 API 使用的方法,例如 Spring、Guava、Zookeeper、Jackson、Servlet 等。