CodeGym /Các khóa học /JAVA 25 SELF /Tạo đối tượng bằng reflection

Tạo đối tượng bằng reflection

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Tạo đối tượng bằng reflection

Đôi khi chúng ta cần tạo đối tượng mà không biết kiểu của nó vào thời điểm biên dịch. Ví dụ, tên lớp đến từ cấu hình, hoặc chúng ta đang viết một framework tổng quát (trình tuần tự hóa, DI‑container). Trong mã thông thường, ta sẽ viết:

User user = new User("Ivan", 25);

Nhưng nếu chúng ta không biết lớp tên là User và không biết các tham số của constructor thì sao? Khi đó reflection sẽ giúp.

Cách cũ: Class.newInstance()

Trước đây trong Java có phương thức Class.newInstance(), tạo đối tượng thông qua constructor public không tham số:

Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.newInstance();

Quan trọng: Phương thức này bị đánh dấu là deprecated từ Java 9 và không được khuyến nghị. Nó không cho phép chọn constructor, che giấu nguyên nhân lỗi và chỉ yêu cầu constructor public không tham số.

Cách hiện đại: qua constructor

Trong mã thực tế, không phải lúc nào cũng có constructor public không tham số. Cách tiếp cận hiện nay — lấy constructor cần thiết qua getConstructor(...) hoặc getDeclaredConstructor(...), sau đó gọi newInstance(...) trên nó.

Ví dụ: tạo đối tượng với tham số

public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
import java.lang.reflect.Constructor;

Class<?> clazz = Class.forName("User");
// Lấy constructor với các tham số String, int
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// Tạo đối tượng, truyền tham số
Object user = constructor.newInstance("Ivan", 25);

System.out.println(user); // toString nếu được định nghĩa

Điều gì đang diễn ra ở đây?

  • Lấy constructor cần thiết theo chữ ký (signature).
  • Gọi newInstance(...) trên nó, truyền các đối số.
  • Nhận về một đối tượng kiểu Object (có thể ép kiểu về User nếu biết kiểu).

Nếu constructor là private?

Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true); // Phép màu! Giờ có thể gọi constructor private.
Object user = constructor.newInstance("Ivan", 25);

Cảnh báo: Không nên lạm dụng — điều này phá vỡ tính đóng gói. Trong ứng dụng theo module (Java 9+) cũng có các hạn chế truy cập.

Bảng so sánh các cách tạo đối tượng

Cách Tốc độ An toàn kiểu Khi sử dụng
new User()
Nhanh nhất Đầy đủ Luôn dùng khi có thể
Constructor.newInstance()
Chậm Mất Framework, plugin
MethodHandle
Trung bình Một phần Thư viện hiệu năng cao
Factory Nhanh Đầy đủ Tạo đối tượng linh hoạt

2. Gọi phương thức qua reflection

Bạn có thể gọi bất kỳ phương thức nào của đối tượng, ngay cả khi không biết tên của nó lúc biên dịch, hoặc khi nó là private.

Lấy phương thức

import java.lang.reflect.Method;

Class<?> clazz = user.getClass();
// Lấy phương thức public theo tên và các kiểu tham số
Method method = clazz.getMethod("getName"); // Không có tham số
Object result = method.invoke(user); // Gọi phương thức không tham số

System.out.println(result); // In ra tên người dùng

Gọi phương thức có tham số

Method setName = clazz.getMethod("setName", String.class);
setName.invoke(user, "Petr"); // Đặt tên mới

Gọi phương thức private

Method secret = clazz.getDeclaredMethod("secretMethod", int.class);
secret.setAccessible(true); // Bỏ chặn truy cập
Object secretResult = secret.invoke(user, 123);

Quan trọng: Tất cả tham số được truyền như một mảng đối tượng (varargs). Nếu phương thức trả về gì đó — kết quả sẽ là Object.

3. Truy cập trường (field) qua reflection

Đôi khi cần đọc hoặc thay đổi giá trị của trường, ngay cả khi nó là private (ví dụ khi tuần tự hóa hoặc kiểm thử).

Lấy trường

import java.lang.reflect.Field;

Class<?> clazz = user.getClass();
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true); // Nếu trường không phải public

// Đọc giá trị
Object age = ageField.get(user);
System.out.println("Tuổi: " + age);

// Thay đổi giá trị
ageField.set(user, 42);
System.out.println("Tuổi mới: " + ageField.get(user));

Làm việc với trường static

Nếu trường là static, hãy truyền vào get/set giá trị null thay cho đối tượng:

Field staticField = clazz.getDeclaredField("counter");
staticField.setAccessible(true);
staticField.set(null, 100); // Đối với trường static không cần đối tượng

4. Hạn chế và ngoại lệ

Làm việc với reflection có thể đi kèm rất nhiều checked‑exception. Phổ biến nhất:

  • ClassNotFoundException — không tìm thấy lớp với tên đó.
  • NoSuchMethodException — không có constructor hoặc phương thức với chữ ký như vậy.
  • NoSuchFieldException — không tìm thấy trường.
  • IllegalAccessException — không có quyền truy cập vào member (ví dụ phương thức private không có setAccessible(true)).
  • InstantiationException — lớp là abstract hoặc interface; không thể tạo đối tượng.
  • InvocationTargetException — lỗi bên trong constructor hoặc phương thức được gọi.

Ví dụ xử lý:

try {
    // ... mã reflection của bạn ...
} catch (ReflectiveOperationException e) {
    e.printStackTrace();
}

Nếu chi tiết không quan trọng, tiện nhất là bắt siêu lớp chung ReflectiveOperationException (Java 7+).

5. Thực hành: mini-program “Trình khởi tạo động”

Ví dụ lớp để thử nghiệm

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private void sayHello() {
        System.out.println("Xin chào, tôi tên là " + name + ", tôi " + age + " tuổi.");
    }
}

Tạo và thao tác động

import java.lang.reflect.*;

public class ReflectionDemo {
    public static void main(String[] args) {
        try {
            // 1. Lấy đối tượng Class theo tên
            Class<?> clazz = Class.forName("Person");

            // 2. Lấy constructor và tạo đối tượng
            Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
            Object person = constructor.newInstance("Alisa", 30);

            // 3. Gọi phương thức private sayHello
            Method sayHello = clazz.getDeclaredMethod("sayHello");
            sayHello.setAccessible(true);
            sayHello.invoke(person); // Xin chào, tôi tên là Alisa, tôi 30 tuổi.

            // 4. Thay đổi trường private name
            Field nameField = clazz.getDeclaredField("name");
            nameField.setAccessible(true);
            nameField.set(person, "Bob");

            // 5. Gọi lại sayHello
            sayHello.invoke(person); // Xin chào, tôi tên là Bob, tôi 30 tuổi.

        } catch (ReflectiveOperationException e) {
            e.printStackTrace();
        }
    }
}

Lưu ý:

  • Mọi thứ được thực hiện mà không cần biết trực tiếp về kiểu Person trong mã.
  • Có thể thay đổi trường private và gọi phương thức private (nếu được phép bởi chính sách bảo mật của JVM và cấu hình module).

6. Điều này liên quan gì tới ứng dụng thực tế?

Reflection là nền tảng của nhiều thư viện và framework phổ biến:

  • JUnit: tìm phương thức có annotation @Test, tạo instance test và gọi phương thức.
  • Spring: DI, tạo bean, autowiring.
  • Jackson, Gson: tuần tự hóa/giải tuần tự các trường của đối tượng.
  • Hibernate: truy cập trường của entity, proxy và lazy loading.

7. Sơ đồ trực quan: đối tượng được tạo qua reflection như thế nào

flowchart TB
    A["Tên lớp (String)"] --> B["Class.forName"]
    B --> C["Class<?>"]
    C --> D["getConstructor(...)"]
    D --> E["Constructor<?>"]
    E --> F["newInstance(...)"]
    F --> G["Object"]

8. Các lỗi thường gặp khi làm việc với reflection

Lỗi số 1: Sai constructor. Bạn yêu cầu constructor với chữ ký sai, ví dụ getConstructor(String.class) trong khi chỉ có constructor với hai tham số — sẽ nhận NoSuchMethodException. Hãy luôn kiểm tra chữ ký!

Lỗi số 2: Thiếu quyền truy cập. Gọi constructor/phương thức private mà không có setAccessible(true) dẫn tới IllegalAccessException. Và nhớ rằng: trong Java 9+ hệ thống module có thể áp thêm các hạn chế.

Lỗi số 3: Vấn đề về kiểu. Tất cả tham số và giá trị trả về qua reflection là Object. Ép kiểu sai — ClassCastException.

Lỗi số 4: Ngoại lệ chưa được xử lý. Reflection ném nhiều checked‑exception. Hãy xử lý chúng, ví dụ bằng ReflectiveOperationException tổng quát, nếu không mã sẽ không biên dịch.

Lỗi số 5: Phá vỡ tính đóng gói. Lạm dụng setAccessible(true) — như “truy cập tủ lạnh của người khác”. Chỉ dùng khi thật sự cần và cân nhắc yêu cầu bảo mật.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION