Ví dụ tạo đối tượng bằng Class.newInstance()

Hãy tưởng tượng bạn được chỉ định tạo một đối tượng bằng cách sử dụng sự phản chiếu. Chúng ta bắt đầu nhé?

Chúng ta sẽ bắt đầu bằng cách viết mã cho lớp mà chúng ta muốn khởi tạo:

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 +
                '}';
    }
}

Đây sẽ là lớp của chúng ta — với một số trường, hàm tạo có tham số, getters và setters, phương thức toString() và khối khởi tạo. Bây giờ, hãy chuyển sang phần thứ hai: tạo một đối tượng bằng cách sử dụng sự phản chiếu. Cách tiếp cận đầu tiên mà chúng ta sẽ xem xét sẽ sử dụng 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());
    }
}

Xuất sắc! Hãy chạy mã của chúng tôi và xem độ tuổi được hiển thị. Nhưng chúng tôi gặp lỗi về hàm tạo mặc định bị thiếu. Hóa ra phương pháp này chỉ cho phép chúng ta lấy một đối tượng được tạo bằng hàm tạo mặc định. Hãy thêm một hàm tạo mặc định cho lớp của chúng ta và kiểm tra lại mã.

Thông báo lỗi:

Mã của hàm tạo mới

public Employee() { }

Sau khi thêm hàm tạo, đây là kết quả:

tuổi là 1

Tuyệt vời! Chúng tôi đã tìm ra cách thức hoạt động của phương pháp này. Bây giờ chúng ta hãy xem dưới mui xe. Bẻ khóa mở tài liệu, chúng tôi thấy rằng phương pháp của chúng tôi đã không được dùng nữa :

Nó cũng có thể ném InstantiationExceptionIllegalAccessException . Theo đó, tài liệu gợi ý rằng chúng ta sử dụng cách tạo đối tượng khác, cụ thể là Constructor.newInstance() . Hãy phân tích chi tiết cách thức hoạt động của lớp Constructor .

phương thức getConstructors và getDeclaredConstructors

Để làm việc với lớp Constructor , trước tiên chúng ta cần lấy một thể hiện. Chúng tôi có hai phương thức cho việc này: getConstructorsgetDeclaredConstructors .

Cái đầu tiên trả về một mảng các hàm tạo công khai và cái thứ hai trả về một mảng gồm tất cả các hàm tạo của lớp.

Hãy cung cấp cho lớp của chúng ta một số quyền riêng tư, hay đúng hơn, hãy tạo một số hàm tạo riêng để giúp minh họa cách các phương thức này hoạt động.

Hãy thêm một số hàm tạo riêng:

private Employee(String name, String surname) {
    this.name = name;
    this.lastName = lastName;
}

Nhìn vào mã, lưu ý rằng một trong các hàm tạo là riêng tư:

Hãy thử nghiệm các phương pháp của chúng tôi:

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

Và chúng tôi nhận được kết quả này:

getConstructors:
public 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)
công khai com.codegym.Employee(java.lang.String,java.lang.String,int)
công khai com.codegym.Employee()

Được rồi, đây là cách chúng ta truy cập vào đối tượng Constructor . Bây giờ chúng ta có thể nói về những gì nó có thể làm.

Lớp java.lang.reflect.Constructor và các phương thức quan trọng nhất của nó

Chúng ta hãy xem các phương pháp quan trọng nhất và cách chúng hoạt động:

Phương pháp Sự miêu tả
getName() Trả về tên của hàm tạo này dưới dạng một chuỗi.
getModifier() Trả về công cụ sửa đổi truy cập Java được mã hóa dưới dạng số.
getExceptionTypes() Trả về một mảng các đối tượng Lớp đại diện cho các loại ngoại lệ được khai báo bởi hàm tạo.
getParameter() Trả về một mảng các đối tượng Tham số đại diện cho tất cả các tham số. Trả về một mảng có độ dài 0 nếu hàm tạo không có tham số.
getParameterTypes() Trả về một mảng các đối tượng Lớp đại diện cho các loại tham số chính thức theo thứ tự khai báo.
getGenericParameterTypes() Trả về một mảng các đối tượng Kiểu đại diện cho các kiểu tham số hình thức theo thứ tự khai báo.

getName() & getModifiers()

Hãy bọc mảng của chúng ta trong một Danh sách để thuận tiện khi làm việc. Chúng ta cũng sẽ viết các phương thức 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;
}

Và phương thức chính của chúng ta , nơi chúng ta sẽ gọi mọi thứ:

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

Và bây giờ chúng tôi thấy tất cả thông tin chúng tôi muốn:

Lớp nhân viên: Trình
xây dựng:
[riêng tư com.codegym.Employee(java.lang.String), công khai
com.codegym.Employee(java.lang.String,java.lang.String,int), công khai com.codegym.Employee() ]
Công cụ sửa đổi :
[riêng tư, công khai, công khai]

getExceptionTypes()

Phương thức này cho phép chúng tôi nhận được một loạt các ngoại lệ mà hàm tạo của chúng tôi có thể đưa ra. Hãy sửa đổi một trong các hàm tạo của chúng ta và viết một phương thức mới.

Ở đây chúng tôi thay đổi một chút hàm tạo hiện tại của mình:

private Employee(String name, String surname) throws Exception {
    this.name = name;
    this.lastName = lastName;
}

Và ở đây, chúng tôi có một phương pháp để nhận các loại ngoại lệ và thêm nó vào 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);

Ở trên, chúng tôi đã truy cập hàm tạo đầu tiên trong danh sách của mình. Chúng ta sẽ thảo luận về cách lấy một hàm tạo cụ thể sau.

Và xem kết quả sau khi chúng ta thêm throws Exception :

Các loại ngoại lệ:
[lớp java.lang.Exception]

Và trước khi thêm ngoại lệ:

Các loại ngoại lệ :
[]

Mọi thứ đều tuyệt vời, nhưng làm cách nào để chúng tôi biết các tham số nào được yêu cầu bởi các nhà xây dựng của chúng tôi? Hãy tìm hiểu điều này là tốt.

getParameters() & getParameterTypes() & getGenericParameterTypes()

Hãy bắt đầu lại bằng cách tinh chỉnh hàm tạo riêng của chúng ta. Bây giờ nó sẽ trông như thế này:

private Employee(String name, String surname, List<String> list) {
    this.name = name;
    this.lastName = lastName;
}

Và chúng tôi có ba phương thức bổ sung: getParameters để nhận thứ tự các tham số và loại của chúng, getParameterTypes để nhận các loại tham số và getGenericParameterTypes để nhận các loại được bao bọc trong generics .

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

Và chúng tôi thêm một số thông tin khác vào phương thức chính vốn đã không quá nhỏ của chúng tôi :

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

Nhìn vào đầu ra, chúng tôi thấy thông tin rất chi tiết về các tham số của các hàm tạo của chúng tôi:

Tham số hàm tạo:
[java.lang.String arg0, java.lang.String arg1, java.util.List<java.lang.String> arg2]
Các loại tham số:
[lớp java.lang.String, lớp java.lang.String, giao diện java.util.List]
Các loại tham số của hàm tạo:
[lớp java.lang.String, lớp java.lang.String, java.util.List<java.lang.String>]

Điều này thể hiện rõ ràng sự khác biệt giữa mỗi phương pháp. Chúng tôi thấy rằng chúng tôi có các tùy chọn riêng biệt để nhận thông tin về các loại tham số, loại được bao bọc và mọi thứ nói chung. Siêu! Bây giờ chúng ta đã làm quen với lớp Constructor , chúng ta có thể quay lại chủ đề chính của bài viết — tạo đối tượng.

Tạo đối tượng bằng Constructor.newInstance()

Cách thứ hai để tạo đối tượng là gọi phương thức newInstance trên hàm tạo. Hãy xem một ví dụ đang hoạt động và xem cách chúng ta có thể lấy một hàm tạo cụ thể.

Nếu bạn muốn lấy một hàm tạo duy nhất, bạn nên sử dụng phương thức getConstructor (đừng nhầm lẫn với getConstructors , phương thức này trả về một mảng gồm tất cả các hàm tạo). Phương thức getConstructor trả về hàm tạo mặc định.

public static void main(String[] args) throws NoSuchMethodException {
    Class employeeClass = Employee.class;
    Constructor<?> employeeConstructor = employeeClass.getConstructor();
    System.out.println(employeeConstructor);
}
công khai com.codegym.Employee()

Và nếu chúng ta muốn lấy một hàm tạo cụ thể, chúng ta cần chuyển các loại tham số của hàm tạo cho phương thức này.

Đừng quên rằng chúng ta chỉ có thể lấy hàm tạo riêng bằng phương thức getDeclaredConstructor .

Constructor<?> employeeConstructor2 = employeeClass.getDeclaredConstructor(String.class, String.class, List.class);
System.out.println(employeeConstructor2);

Đây là cách chúng ta có thể có được một hàm tạo cụ thể. Bây giờ, hãy thử tạo các đối tượng bằng cách sử dụng các hàm tạo private và public.

Nhà xây dựng công cộng:

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

Kết quả là một đối tượng mà chúng ta có thể làm việc với:

công khai com.codegym.Employee(java.lang.String,java.lang.String,int)
Nhân viên{name='Rob' họ='Stark', tuổi=10}

Mọi thứ hoạt động tuyệt vời! Bây giờ chúng ta sẽ thử với một private constructor:

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

Kết quả là một lỗi về quyền riêng tư của nhà xây dựng của chúng tôi:

Java không thể tạo một đối tượng bằng cách sử dụng hàm tạo này, nhưng thực sự có một điều kỳ diệu mà chúng ta có thể thực hiện trong phương thức chính . Chúng tôi có thể cấp độ truy cập của nhà xây dựng của chúng tôi, làm cho nó có thể tạo các đối tượng của lớp của chúng tôi:

declaredConstructor.setAccessible(true);

Kết quả tạo đối tượng

riêng tư com.codegym.Employee(java.lang.String,java.lang.String,java.util.List)
Nhân viên{name='Rob',họ='Stark', tuổi=-1}

Chúng tôi không đặt tuổi trong hàm tạo của mình, vì vậy nó vẫn giữ nguyên như khi nó được khởi tạo.

Tuyệt vời, chúng ta hãy tóm tắt!

Lợi ích của việc tạo đối tượng bằng Constructor.newInstance()

Cả hai phương pháp đều có cùng tên, nhưng có sự khác biệt giữa chúng:

Class.newInstance() Constructor.newInstance()
Chỉ có thể gọi hàm tạo không có đối số . Có thể gọi bất kỳ hàm tạo nào bất kể số lượng tham số.
Yêu cầu hàm tạo phải hiển thị. Cũng có thể gọi các hàm tạo riêng trong một số trường hợp nhất định.
Ném bất kỳ ngoại lệ nào (được chọn hay không) được khai báo bởi hàm tạo. Luôn kết thúc một ngoại lệ được ném bằng InvocationTargetException .

Vì những lý do này, Constructor.newInstance() được ưu tiên hơn Class.newInstance() và là phương thức được sử dụng bởi nhiều khung và API khác nhau như Spring, Guava, Zookeeper, Jackson, Servlet, v.v.