Java Động Tĩnh: Phản Chiếu, Liệt Kê và Biểu Thức Lambda

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ớpMụ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.
MethodMô 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:

  1. 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).
  2. Qua tên lớp tĩnh: User.class — an toàn, hiệu suất cao, không ném ngoại lệ.
  3. 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ém IllegalArgumentException nế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à return nế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.

Thẻ: java-reflection java-enum lambda-expressions Java-8 functional-programming

Đăng vào ngày 10 tháng 7 lúc 02:37