Java 8 đã được sử dụng rộng rãi trong một thời gian dài, nhưng một trong những tính năng mới của nó - biểu thức Lambda - vẫn chưa được tận dụng triệt để. Do thói quen cá nhân, trong các dự án thực tế, tôi vẫn sử dụng cú pháp truyền thống cũ, nhưng không thể phủ nhận rằng việc sử dụng biểu thức Lambda thực sự có thể đơn giản hóa mã nguồn của chúng ta. Hơn nữa, các đồng nghiệp xung quanh thường xuyên sử dụng các đoạn mã Lambda, khiến tôi cảm thấy hơi xa lạ. Hôm nay, nhân lúc không quá bận rộn, tôi quyết định tìm hiểu kỹ hơn về tính năng này.
Cách tiếp cận lập trình hàm Biểu thức Lambda thực chất là một tư duy lập trình hàm. Ngôn ngữ Java nhấn mạnh lập trình hướng đối tượng, yêu cầu phải thực hiện một việc gì đó thông qua một đối tượng (ví dụ: nếu bạn muốn lấy một số ngẫu nhiên, bạn cần sử dụng đối tượng Math và gọi phương thức random() của nó). Ngược lại, lập trình hàm tập trung vào "việc cần làm" (ví dụ: nếu bạn muốn đến三亚 tắm nắng, bạn có thể chọn ô tô, xe đạp hoặc đi bộ. Cả ba cách đều có thể đến đích, và điều bạn quan tâm chỉ là đến được三亚, không phải cách thức bạn đi đến đó). Điều này có nghĩa là: lập trình hàm làm giảm sự phức tạp của cú pháp hướng đối tượng.
Phát triển ví dụ Lambda Hãy cùng xem một ví dụ đơn giản để cảm nhận sức mạnh của Lambda: Trong lập trình đa luồng, chúng ta thường truyền một lớp triển khai của giao diện Runnable vào lớp Thread:
/**
* Lớp triển khai giao diện Runnable
*/
public static class TaskRunner implements Runnable {
@Override
public void execute() {
System.out.println("Luồng " + Thread.currentThread().getName() + " đã được tạo");
}
}
public static void main(String[] args) {
// Tạo một đối tượng của lớp triển khai giao diện Runnable
TaskRunner task = new TaskRunner();
// Truyền đối tượng triển khai vào lớp Thread
Thread thread = new Thread(task);
thread.start();
}
Sau đó, chúng ta nhận thấy cách trên khá phức tạp. Tôi chỉ muốn truyền phương thức execute() của giao diện Runnable vào Thread, nhưng lại phải tạo lớp triển khai và khởi tạo đối tượng. Quá rườm rà! Vì vậy, chúng ta bắt đầu sử dụng lớp nội tại ẩn danh để đơn giản hóa:
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Luồng " + Thread.currentThread().getName() + " đã được tạo");
}
}).start();
}
Cách này khá tiện lợi, nhưng vẫn chưa đủ gọn gàng. Đối với tư duy hướng đối tượng, chúng ta cần đơn giản hóa thêm:
public static void main(String[] args) {
new Thread(() -> {
System.out.println("Luồng " + Thread.currentThread().getName() + " đã được tạo");
}).start();
}
Cả ba cách trên đều đạt được kết quả tương tự, nhưng sự giảm thiểu về lượng mã nguồn là rất rõ ràng. Phân tích: Trong biểu thức Lambda trên, () -> {……} đã thay thế trực tiếp phương thức run() trong giao diện Runnable. Phương thức run() là một phương thức không tham số và không giá trị trả về.
Cú pháp chuẩn Lambda Điều kiện sử dụng Lambda:
Phải có giao diện, và giao diện đó chỉ định nghĩa một phương thức trừu tượng duy nhất. Nếu không, khi sử dụng Lambda để đơn giản hóa, nó không biết đang triển khai phương thức nào. Kiểu của tham số hoặc biến cục bộ phải là kiểu của giao diện tương ứng với biểu thức Lambda, mới có thể sử dụng Lambda làm thể hiện của giao diện đó.
Cú pháp chuẩn của biểu thức Lambda có thể được chia thành ba phần:
(Tham số) -> {Mã logic}
Phần tham số
() là các tham số chúng ta cần truyền. Nếu có tham số, hãy viết chúng vào trong ngoặc đơn này. Nếu không có tham số, hãy để trống bên trong ngoặc đơn. Nếu có nhiều tham số, hãy sử dụng dấu phẩy , để phân tách. Ví dụ: (tham số1, tham số2).
Phần ký hiệu mũi tên
-> đại diện cho việc truyền các tham số bên trong ngoặc đơn vào phần {} phía sau, tức là truyền các tham số phía trước vào mã phía sau.
Phần thân phương thức
{} đại diện cho thân phương thức, bên trong là việc triển khai mã logic cụ thể. Trong ví dụ trên, thân phương thức chỉ đơn giản là System.out.println() in ra một câu.
Ví dụ Lambda Ví dụ: Sắp xếp theo tuổi tăng dần
@SpringBootTest
class ApplicationTest {
public static void main(String[] args) {
Employee[] staff = {
new Employee(25, "An Nguyễn"),
new Employee(40, "Bình Trần"),
new Employee(30, "Chi Lê")
};
/**
* Cách 1: Phương pháp truyền thống
*/
/*Arrays.sort(staff, new Comparator<Employee>() {
@Override
public int compare(Employee e1, Employee e2) {
return e1.getAge() - e2.getAge();
}
});*/
System.out.println();
/**
* Cách 2: Viết bằng Lambda
*/
Arrays.sort(staff, (Employee e1, Employee e2) -> {
return e1.getAge() - e2.getAge();
});
/**
* In mảng nhân viên
*/
for (Employee emp : staff) {
System.out.println(emp);
}
}
/**
* Định nghĩa một lớp thực thể
*/
public static class Employee {
private int age;
private String name;
public Employee(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Employee{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
Trong ví dụ trên, chúng ta có thể sắp xếp mảng nhân viên theo tuổi tăng dần bằng cả hai cách, nhưng việc sử dụng Lambda giúp mã nguồn gọn gàng hơn. Thực tế, biểu thức Lambda ở trên chưa phải là dạng đơn giản nhất. Dưới đây là các quy tắc rút gọn của Lambda.
Quy tắc rút gọn Lambda
Phần danh sách tham số:
Kiểu của tham số trong ngoặc đơn có thể bỏ qua (biểu thức Lambda có thể suy luận kiểu tham số dựa trên ngữ cảnh) Nếu trong ngoặc đơn chỉ có một tham số, có thể bỏ qua () và kiểu tham số. Lưu ý: Nếu không có tham số nào trong ngoặc đơn, không thể bỏ qua ().
Phần thân phương thức:
Nếu trong thân phương thức chỉ có một dòng mã (một ; là một dòng mã), có thể bỏ qua {}, return và ;. Lưu ý: Ba thứ này phải bỏ cùng lúc, nếu không sẽ gây lỗi.
Vì vậy, trong ví dụ trên, chúng ta có thể đơn giản hóa thêm:
/**
* Cách 2: Viết bằng Lambda (đơn giản hóa thêm)
*/
Arrays.sort(staff, (e1, e2) -> e1.getAge() - e2.getAge());
Đó là những hiểu biết cơ bản về biểu thức Lambda. Để sử dụng biểu thức này, trước hết bạn cần có tư duy về lập trình hàm, bản chất của nó thực chất là một thể hiện của giao diện hàm (giao diện chỉ có một phương thức trừu tượng).