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 |
|---|---|---|---|
|
Nhanh nhất | Đầy đủ | Luôn dùng khi có thể |
|
Chậm | Mất | Framework, plugin |
|
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.
GO TO FULL VERSION