Phản chiếu (Reflection)
Phản chiếu trong Java là khả năng của chương trình khám phá và thao tác với cấu trúc lớp, phương thức, trường và constructor tại thời điểm chạy, chứ không chỉ khi biên dịch. Nhờ cơ chế này, một ứng dụng có thể:
- Xác định kiểu thực tế của một đối tượng dù biến tham chiếu được khai báo dưới dạng lớp cha hoặc giao diện.
- Tạo thể hiện của lớp mà tên lớp chỉ biết ở runtime.
- Truy cập và sửa đổi các thành viên riêng tư (private) — sau khi bật quyền truy cập qua
setAccessible(true). - Gọi phương thức hoặc khởi tạo đối tượng mà không cần biết chi tiết triển khai trước khi biên dịch.
Các lớp cốt lõi trong API phản chiếu
| Lớp | Mục đích |
|---|---|
Class<T> | Đại diện cho thực thể lớp/interface tại runtime; là cầu nối để truy vấn mọi thông tin về lớp. |
Field | Đóng gói một thuộc tính (field) của lớp, hỗ trợ đọc/ghi giá trị ngay cả khi trường là private. |
Method | Mô tả một phương thức; cho phép gọi động (invoke) với tham số tùy ý. |
Constructor<T> | Đại diện cho một hàm dựng; dùng để khởi tạo đối tượng với tham số cụ thể. |
Lấy đối tượng Class
Có ba cách phổ biến để lấy đối tượng Class:
- Qua tên chuỗi đầy đủ:
Class.forName("com.example.User")— hữu ích khi tên lớp chỉ biết ở runtime (ví dụ: từ cấu hình). - Qua tên lớp tĩnh:
User.class— an toàn, hiệu suất cao, không ném ngoại lệ. - Qua đối tượng hiện hữu:
userInstance.getClass()— phù hợp khi đã có thể hiện và cần kiểm tra kiểu thực tế.
Ví dụ minh họa với lớp Person
public class Person {
private String fullName = "Nguyen Van A";
public int age = 25;
public Person() { System.out.println("Khởi tạo mặc định"); }
private Person(String name, int age) {
this.fullName = name;
this.age = age;
System.out.println("Khởi tạo riêng tư");
}
private void greet() {
System.out.println("Xin chào!");
}
public void introduce() {
System.out.println("Tôi là " + fullName);
}
}
1. Khởi tạo đối tượng bằng constructor riêng tư
try {
Class<?> clazz = Class.forName("Person");
Constructor<?> ctor = clazz.getDeclaredConstructor(String.class, int.class);
ctor.setAccessible(true); // Bật quyền truy cập
Object person = ctor.newInstance("Tran Thi B", 30);
} catch (Exception e) {
e.printStackTrace();
}
2. Đọc và ghi trường riêng tư
try {
Class<?> clazz = Person.class;
Field nameField = clazz.getDeclaredField("fullName");
nameField.setAccessible(true);
Person p = new Person();
nameField.set(p, "Le Van C");
System.out.println(nameField.get(p)); // In ra "Le Van C"
} catch (Exception e) {
e.printStackTrace();
}
3. Gọi phương thức riêng tư
try {
Method greetMethod = Person.class.getDeclaredMethod("greet");
greetMethod.setAccessible(true);
Person p = new Person();
greetMethod.invoke(p); // In ra "Xin chào!"
} catch (Exception e) {
e.printStackTrace();
}
Ưu – nhược điểm của phản chiếu
- Ưu điểm: Hỗ trợ xây dựng framework linh hoạt (Spring, Hibernate), mở rộng tính năng mà không cần thay đổi mã nguồn gốc, giảm phụ thuộc giữa các thành phần.
- Nhược điểm: Mất kiểm soát an ninh (bỏ qua kiểm tra truy cập), hiệu năng thấp hơn gọi trực tiếp, khó gỡ lỗi và bảo trì do vi phạm nguyên tắc đóng gói.
Liệt kê (Enum)
Liệt kê trong Java là một kiểu dữ liệu đặc biệt biểu diễn tập hợp các hằng số cố định, kế thừa ngầm từ java.lang.Enum. Mỗi hằng trong enum là một thể hiện duy nhất (singleton) của lớp đó.
Khái báo và sử dụng
public enum Status {
PENDING, PROCESSING, COMPLETED, FAILED
}
Các phương thức tiêu chuẩn:
Status.values(): Trả về mảng chứa tất cả các giá trị.Status.valueOf("PENDING"): Chuyển chuỗi thành giá trị enum tương ứng (némIllegalArgumentExceptionnếu không tồn tại).PENDING.ordinal(): Trả về vị trí thứ tự (bắt đầu từ 0).PENDING.compareTo(COMPLETED): So sánh theo thứ tự khai báo.
Enum với trường và phương thức
public enum HttpMethod {
GET(200), POST(201), PUT(204), DELETE(200);
private final int statusCode;
HttpMethod(int code) {
this.statusCode = code;
}
public int getStatusCode() {
return statusCode;
}
}
Lưu ý: Constructor enum luôn là private; không thể tạo thể hiện mới bằng new hay phản chiếu.
Biểu thức Lambda
Biểu thức Lambda là cú pháp ngắn gọn để triển khai giao diện chức năng (functional interface) — tức giao diện chỉ có một phương thức trừu tượng (SAM). Việc thêm chú thích @FunctionalInterface giúp trình biên dịch kiểm tra ràng buộc này.
Cú pháp cơ bản
(tham_số) -> thân_phương_thức
Ví dụ:
() -> System.out.println("Hello")— không tham số.name -> System.out.println(name)— một tham số, kiểu suy luận.(x, y) -> x + y— hai tham số, trả về giá trị.
Quy tắc rút gọn
- Bỏ dấu ngoặc đơn nếu chỉ một tham số và kiểu được suy luận.
- Bỏ dấu ngoặc nhọn và
returnnếu thân là một biểu thức đơn. - Kiểu tham số có thể bỏ qua nếu trình biên dịch suy luận được.
Ứng dụng với Collection
Java 8 bổ sung nhiều phương thức mặc định vào Collection để tích hợp với Lambda:
// Sắp xếp ngược với PriorityQueue
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
// Duyệt danh sách
List<String> names = Arrays.asList("An", "Binh", "Cuong");
names.forEach(name -> System.out.print(name + " "));
// Lọc và ánh xạ với Stream
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.collect(Collectors.toList());
Chú ý về việc bắt biến (Variable Capture)
Biểu thức Lambda có thể tham chiếu đến biến cục bộ từ phạm vi bao quanh — nhưng chỉ với điều kiện biến đó là effectively final (không bị gán lại sau khi khai báo). Việc thay đổi giá trị biến bên trong Lambda hoặc trước khi Lambda được tạo đều gây lỗi biên dịch.