Trong lập trình Java, Lambda Expression thực chất là một hàm ẩn danh (anonymous function). Bạn có thể hình dung nó như một đoạn mã có thể được truyền đi khắp nơi giống như cách chúng ta truyền dữ liệu. Việc áp dụng Lambda giúp mã nguồn trở nên ngắn gọn, linh hoạt và nâng cao đáng kể khả năng biểu đạt của ngôn ngữ Java. Về mặt bản chất, Lambda Expression chính là một thể hiện (instance) của Functional Interface (giao diện hàm). Do đó, nếu không có Functional Interface, Lambda sẽ không có ngữ cảnh để hoạt động.
Dưới đây là các ví dụ minh họa sự khác biệt giữa cách viết truyền thống và cách viết sử dụng Lambda:
@Test
public void demonstrateLambdaBasics() {
// Cách viết truyền thống: Sử dụng Anonymous Inner Class
Runnable traditionalTask = new Runnable() {
@Override
public void run() {
System.out.println("Executing traditional background task...");
}
};
traditionalTask.run();
// Cách viết hiện đại: Sử dụng Lambda Expression
Runnable modernTask = () -> System.out.println("Executing modern background task...");
modernTask.run();
}
@Test
public void demonstrateComparatorAndMethodReference() {
// Anonymous Inner Class
Comparator<Double> traditionalComparator = new Comparator<Double>() {
@Override
public int compare(Double d1, Double d2) {
return Double.compare(d1, d2);
}
};
// Method Reference (Tham chiếu phương thức)
Comparator<Double> methodRefComparator = Double::compare;
// Lambda với kiểu dữ liệu tường minh
Comparator<Double> explicitLambda = (Double d1, Double d2) -> Double.compare(d1, d2);
// Lambda với kiểu dữ liệu được suy luận
Comparator<Double> inferredLambda = (d1, d2) -> Double.compare(d1, d2);
System.out.println(traditionalComparator.compare(1.5, 2.5));
System.out.println(methodRefComparator.compare(3.14, 1.1));
System.out.println(explicitLambda.compare(0.5, 0.9));
System.out.println(inferredLambda.compare(9.9, 8.8));
}
Các đoạn mã trên tạo ra các thể hiện của lớp ẩn danh để triển khai các giao diện Runnable và Comparator. Khi một giao diện chỉ chứa duy nhất một phương thức trừu tượng (điều kiện để trở thành Functional Interface), việc chuyển đổi sang Lambda sẽ giúp mã nguồn sạch hơn rất nhiều. Trong thực tế phát triển phần mềm, các lập trình viên thường ưu tiên Lambda Expression vì tính ngắn gọn và dễ đọc mà nó mang lại.
Toán tử và các định dạng cú pháp của Lambda
Toán tử -> được gọi là toán tử Lambda (arrow operator). Cấu trúc của nó được chia thành hai phần:
- Vế trái của
->: Danh sách tham số của Lambda, tương ứng với danh sách tham số của phương thức trừu tượng trong giao diện. - Vế phải của
->: Thân của Lambda (Lambda body), tương ứng với phần triển khai (method body) của phương thức trừu tượng đó.
Định dạng 1: Không có tham số và không trả về giá trị
Runnable noParamTask = () -> System.out.println("Task without parameters executed.");
Định dạng 2: Có một tham số và không trả về giá trị
Consumer<String> singleParamConsumer = (String text) -> System.out.println("Consuming: " + text);
singleParamConsumer.accept("Data payload");
Định dạng 3: Lược bỏ kiểu dữ liệu nhờ cơ chế Type Inference (Suy luận kiểu)
Trình biên dịch Java có khả năng tự động suy luận kiểu dữ liệu dựa trên ngữ cảnh, do đó bạn không cần phải khai báo tường minh.
Comparator<String> stringComparator = (s1, s2) -> s1.compareTo(s2);
Consumer<Integer> inferredConsumer = (number) -> System.out.println("Number: " + number);
Định dạng 4: Lược bỏ dấu ngoặc đơn khi chỉ có một tham số
Consumer<String> simplifiedConsumer = text -> System.out.println("Simplified: " + text);
Định dạng 5: Nhiều tham số, nhiều câu lệnh thực thi và có giá trị trả về
Comparator<Integer> complexComparator = (val1, val2) -> {
System.out.println("Comparing " + val1 + " and " + val2);
return Integer.compare(val1, val2);
};
System.out.println(complexComparator.compare(10, 20));
Định dạng 6: Lược bỏ dấu ngoặc nhọn và từ khóa return khi chỉ có một câu lệnh
Comparator<Integer> conciseComparator = (val1, val2) -> Integer.compare(val1, val2);
Các quy tắc rút gọn cú pháp
- Đối với vế trái (Danh sách tham số): Kiểu dữ liệu của các tham số có thể được lược bỏ hoàn toàn do trình biên dịch hỗ trợ suy luận kiểu. Ngoài ra, nếu danh sách chỉ chứa đúng một tham số, cặp dấu ngoặc đơn
()bao quanh tham số đó cũng có thể bỏ qua. - Đối với vế phải (Thân hàm): Thông thường, thân hàm cần được bao bọc bởi một cặp dấu ngoặc nhọn
{}. Tuy nhiên, nếu thân hàm chỉ chứa duy nhất một câu lệnh thực thi (kể cả khi đó là câu lệnhreturn), bạn có thể lược bỏ cả cặp dấu ngoặc nhọn{}lẫn từ khóareturn.