1. Reflection là gì
Reflection trong Java là một cơ chế cho phép chương trình khảo sát và thậm chí thay đổi cấu trúc của chính nó trong thời gian chạy. Nó giống như bạn có thể nhìn vào bên trong mình và tự hỏi: “Mình có những field và method nào? Mình có constructor nào? Rốt cuộc mình là lớp gì?” — và thậm chí gọi một method private của chính mình.
Định nghĩa chính thức:
Reflection là một API cho phép nhận thông tin về các lớp, interface, field, method và constructor trong thời gian chạy của chương trình, cũng như làm việc với chúng.
Vì sao cần reflection?
- Framework: Spring, Hibernate, JUnit dùng reflection cho “ma thuật”: tự động tạo đối tượng, tiêm dependency, gọi method theo tên và theo annotation.
- Tuần tự hóa: Chuyển đối tượng sang JSON/XML và ngược lại — cần biết field và có thể truy cập chúng.
- Kiểm thử: Tự động tìm và gọi các method có annotation (ví dụ @Test).
- Nạp lớp động: Nạp lớp theo tên lúc runtime (plugin, driver).
- IDE và phân tích mã: Tự động gợi ý, inspection, refactor.
Ví dụ đời thường
public class Person {
private String name;
private int age;
public void sayHello() {
System.out.println("Xin chào, tên tôi là " + name);
}
}
Với reflection, trong thời gian chạy ta có thể biết rằng lớp Person có các field name và age, cũng như method sayHello. Hơn nữa, có thể thay đổi giá trị của field private hoặc gọi method theo tên.
Một chút hài hước
Có thể nói reflection giống như trong “The Matrix”: bạn nhận ra toàn bộ code của mình cũng là dữ liệu có thể khảo sát và thay đổi ngay khi đang chạy. Chỉ đừng quên “viên thuốc đỏ” về bảo mật!
2. Lớp Class: trái tim của reflection
Trong Java, mỗi đối tượng và mỗi kiểu trong thời gian chạy đều gắn với một đối tượng đặc biệt thuộc kiểu Class. Đối tượng này chính là “siêu thông tin” về kiểu. Lớp java.lang.Class<T> là điểm vào trung tâm của thế giới reflection.
Làm thế nào để lấy đối tượng Class?
Có vài cách:
- Qua .class
Cách trực tiếp và an toàn nhất khi lớp đã biết tại thời điểm biên dịch:Class<Person> personClass = Person.class; - Qua đối tượng: getClass()
Khi bạn đã có một instance:
Ở đây clazz là đối tượng Class mô tả lớp thực của đối tượng p.Person p = new Person(); Class<?> clazz = p.getClass(); - Qua chuỗi: Class.forName()
Khi tên lớp chỉ biết trong thời gian chạy:
Phù hợp để nạp plugin, driver và các tình huống động khác.Class<?> clazz = Class.forName("com.example.Person");
Ví dụ: Lấy tên lớp
Person p = new Person();
Class<?> clazz = p.getClass();
System.out.println(clazz.getName()); // Sẽ in: Person hoặc com.example.Person
Bảng: Các cách chính để lấy đối tượng Class
| Cách lấy | Khi dùng | Ví dụ |
|---|---|---|
|
Khi lớp đã biết ở giai đoạn biên dịch | |
|
Khi có đối tượng | |
|
Khi tên lớp chỉ biết lúc runtime | |
Sơ đồ: Đối tượng và Class của nó
+-------------------+
| Person p |
+-------------------+
|
v
+-------------------+
| p.getClass() |
+-------------------+
|
v
+-------------------+
| Class<Person> |
+-------------------+
Kiểm tra kiểu thông qua Class
Đôi khi cần biết một đối tượng có thuộc về một lớp cụ thể hay không:
if (p.getClass() == Person.class) {
System.out.println("Đây chắc chắn là Person!");
}
Hoặc tính đến kế thừa — dùng toán tử instanceof thông thường:
if (p instanceof Person) {
// hoạt động như bình thường
}
3. Khi nào cần reflection, và khi nào thì không
Sức mạnh lớn đi kèm trách nhiệm lớn. Reflection là công cụ mạnh, nhưng chỉ nên dùng khi thực sự cần thiết.
Những nơi reflection là cần thiết
- Framework và thư viện: Spring, Hibernate, JUnit, Jackson — tự động hóa, “ma thuật” và ít code “thủ công” hơn.
- Tuần tự hóa: Cần biết các field của đối tượng để chuyển sang JSON/XML.
- Plugin và mở rộng: Các lớp được nạp động và cấu trúc của chúng không biết trước.
Vì sao không nên lạm dụng reflection
- Mất an toàn kiểu: Lỗi chỉ xuất hiện khi runtime.
- Khó bảo trì: Code trở nên kém rõ ràng.
- Hiệu năng: Chậm hơn gọi trực tiếp.
- Bảo mật: Có thể vượt qua đóng gói và lộ dữ liệu private.
Ví dụ: Khi không cần reflection
p.sayHello();
Ví dụ: Khi reflection là “must‑have”
Bạn viết một mini‑framework cho kiểm thử: người dùng đánh dấu các method bằng annotation @Test, còn chương trình phải tìm và gọi chúng tự động — không có reflection thì không làm được.
4. Trình diễn: làm quen với Class
Ví dụ: In tên lớp và superclass của nó
public class ReflectionDemo {
public static void main(String[] args) {
String s = "Hello, reflection!";
Class<?> clazz = s.getClass();
System.out.println("Tên lớp: " + clazz.getName());
System.out.println("Tên đơn giản của lớp: " + clazz.getSimpleName());
System.out.println("Gói: " + clazz.getPackageName());
System.out.println("Lớp cha: " + clazz.getSuperclass().getName());
}
}
Kết quả:
Tên lớp: java.lang.String
Tên đơn giản của lớp: String
Gói: java.lang
Lớp cha: java.lang.Object
Ví dụ: Lấy Class cho kiểu nguyên thủy
Class<Integer> intClass = int.class;
System.out.println(intClass.getName()); // int
Class<?> doubleClass = double.class;
System.out.println(doubleClass.getName()); // double
Ví dụ: Lấy Class cho mảng
int[] arr = new int[10];
Class<?> arrClass = arr.getClass();
System.out.println(arrClass.getName()); // [I (định dạng đặc thù cho mảng)
Ví dụ: Kiểm tra thuộc tính kiểu
if (arrClass.isArray()) {
System.out.println("Đây là mảng!");
}
5. Thực hành: mini‑chương trình “Đây là lớp gì?”
Viết một chương trình nhận tên đầy đủ của lớp (ví dụ, "java.util.ArrayList") và in thông tin chính về nó.
import java.util.Scanner;
public class ClassInfoPrinter {
public static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(System.in);
System.out.print("Nhập tên đầy đủ của lớp: ");
String className = scanner.nextLine();
Class<?> clazz = Class.forName(className);
System.out.println("Tên lớp: " + clazz.getName());
System.out.println("Gói: " + clazz.getPackageName());
System.out.println("Lớp cha: " + clazz.getSuperclass().getName());
Class<?>[] interfaces = clazz.getInterfaces();
System.out.print("Triển khai các interface: ");
for (Class<?> i : interfaces) {
System.out.print(i.getName() + " ");
}
System.out.println();
}
}
Ví dụ chạy:
Nhập tên đầy đủ của lớp: java.util.ArrayList
Tên lớp: java.util.ArrayList
Gói: java.util
Lớp cha: java.util.AbstractList
Triển khai các interface: java.util.List java.util.RandomAccess java.lang.Cloneable java.io.Serializable
Một vài sự thật thú vị về Class và reflection
- Mỗi kiểu đã được nạp trong JVM có một đại diện chung — đối tượng Class. Tất cả instance của cùng một kiểu dùng chung một Class<T>.
- Có thể xác định loại của kiểu: isInterface(), isEnum(), isArray(), isPrimitive() và các hàm khác.
- Nếu chỉ định sai tên trong Class.forName, bạn sẽ nhận ngoại lệ ClassNotFoundException.
- Với reflection có thể tạo đối tượng ngay cả khi không biết lớp ở thời điểm biên dịch — chúng ta sẽ bàn trong bài tiếp theo.
6. Lỗi thường gặp khi mới làm quen với reflection
Lỗi số 1: Nghĩ rằng reflection là nhanh.
Thực tế reflection chậm hơn gọi method và truy cập field trực tiếp. Đừng dùng cho mọi việc lặt vặt.
Lỗi số 2: Cố lấy Class cho một lớp không tồn tại.
Nếu gõ sai tên, bạn sẽ nhận ClassNotFoundException. Hãy xử lý ngoại lệ này.
Lỗi số 3: Nhầm lẫn giữa đối tượng Class và instance.
Đối tượng Class mô tả cấu trúc của kiểu, chứ không phải là instance của nó.
Lỗi số 4: Dùng reflection khi không cần thiết.
Nếu có thể dùng code thông thường — hãy làm như vậy. Reflection cần cho các tình huống động, framework, plugin và các tác vụ tương tự.
Lỗi số 5: Xử lý bất cẩn với field và method private.
Reflection có thể vượt qua đóng gói, dễ dẫn đến lỗi và vấn đề bảo mật, đặc biệt trong các dự án lớn.
GO TO FULL VERSION