Biểu Thức Lambda (Lambda Expression)
"Trong Java, biểu thức Lambda là một tính năng mạnh mẽ được giới thiệu từ Java 8, giúp viết mã ngắn gọn và dễ hiểu hơn. Lambda được sử dụng để biểu diễn một hàm hoặc hành động có thể được truyền như một đối số hoặc được sử dụng trong các biểu thức. Dưới đây là những điều cơ bản bạn cần biết về biểu thức Lambda trong Java:"
Ⅰ. Đặc điểm của biểu thức Lambda
↳ Ngắn gọn và rõ ràng: Biểu thức Lambda cho phép viết mã ngắn gọn hơn so với việc sử dụng lớp ẩn danh.
↳ Không cần định nghĩa lại phương thức: Với biểu thức Lambda, bạn không cần phải định nghĩa lại phương thức, chỉ cần viết mã thực thi.
↳ Không tạo tập tin .class: Trình biên dịch không tạo tập tin .class cho biểu thức Lambda, giúp tiết kiệm bộ nhớ và tăng hiệu suất.
Ⅱ. Cú pháp của biểu thức Lambda
Lambda Expression có cú pháp như sau:
Cú Pháp
(danh_sách_tham_số) -> Thân_của_lambda
Hoặc, với khối mã nhiều dòng:
Cú Pháp
(danh_sách_tham_số) -> {
// nhiều câu lệnh
}
Biểu thức lambda được tạo thành từ các phần sau:
↳ (danh_sách_tham_số): Được đặt trong dấu ngoặc tròn (). Có thể bỏ qua kiểu dữ liệu của tham số. Nếu chỉ có một tham số, bạn có thể bỏ qua dấu ngoặc tròn.
↳ Toán tử mũi tên ->: Ngăn cách danh sách tham số với thân của biểu thức lambda.
↳ Thân_của_lambda: Có thể là một biểu thức đơn giản hoặc một khối lệnh được đặt trong ngoặc nhọn .
↳ Nếu là biểu thức đơn: Trình biên dịch Java sẽ tự động đánh giá biểu thức và trả về giá trị.
↳ Nếu là khối lệnh: Bạn cần sử dụng câu lệnh return để trả về giá trị.
Ví dụ cơ bản
Dưới đây là một ví dụ cơ bản về việc sử dụng Lambda Expression để triển khai giao diện Runnable:
Ví Dụ
// Trước Java 8
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Runnable trước Java 8");
}
};
// Từ Java 8
Runnable r2 = () -> System.out.println("Runnable với Lambda Expression");
public class Example {
public static void main(String[] args) {
r1.run();
r2.run();
}
}
Ⅲ. Khi nào nên sử dụng Lambda?
↳ Các giao diện chức năng (Functional Interface): Lambda thường được sử dụng với các giao diện chỉ có một phương thức trừu tượng.
↳ Các API sử dụng lambda: Nhiều API trong Java 8 và các phiên bản sau đã được thiết kế để làm việc với lambda, như Stream API, Collections, ...
↳ Khi muốn viết code ngắn gọn và dễ đọc: Lambda giúp loại bỏ nhiều boilerplate code và làm cho code trở nên rõ ràng hơn.
Ⅳ. Các giao diện chức năng (Functional Interface)
Trong Java, một giao diện chức năng (Functional Interface) là một giao diện chỉ có một phương thức trừu tượng. Những giao diện này được thiết kế để sử dụng với biểu thức lambda và method references, cho phép mã ngắn gọn và rõ ràng hơn. Giao diện chức năng được đánh dấu bằng annotation @FunctionalInterface, nhưng việc sử dụng annotation này không bắt buộc. Annotation @FunctionalInterface chỉ là để đảm bảo rằng giao diện tuân thủ các quy tắc của một giao diện chức năng.
Dưới đây là một số giao diện chức năng phổ biến trong Java:
1. Runnable:
Là một giao diện chức năng trong Java, được sử dụng để định nghĩa các tác vụ có thể thực thi trong các luồng (threads). Giao diện này có một phương thức duy nhất là run, mà bạn cần phải triển khai để xác định công việc mà luồng sẽ thực hiện.
Cú Pháp
@FunctionalInterface
public interface Runnable {
void run();
}
2. Callable<V>:
Là một giao diện chức năng trong Java, được giới thiệu từ Java 5 trong gói java.util.concurrent. Nó tương tự như Runnable, nhưng khác biệt ở chỗ nó có thể trả về một giá trị và có thể ném ra một ngoại lệ.
Cú Pháp
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
3. Comparator<T>:
Là một giao diện chức năng được sử dụng để so sánh hai đối tượng của loại T để xác định thứ tự của chúng. Nó được sử dụng chủ yếu để sắp xếp và so sánh các đối tượng trong các cấu trúc dữ liệu như danh sách (List), tập hợp (Set), và mảng (Array).
Cú Pháp
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
// Các phương thức mặc định
boolean equals(Object obj);
default Comparator<T> reversed() {
// Implementation
}
}
4. ActionListener:
Là một giao diện chức năng trong Java, được sử dụng để định nghĩa các tác vụ có thể thực thi trong các luồng (threads). Giao diện này có một phương thức duy nhất là run, mà bạn cần phải triển khai để xác định công việc mà luồng sẽ thực hiện.
Cú Pháp
@FunctionalInterface
public interface ActionListener extends EventListener {
void actionPerformed(ActionEvent e);
}
5. Các giao diện chức năng trong gói java.util.function:
Là một giao diện chức năng trong Java, được sử dụng để định nghĩa các tác vụ có thể thực thi trong các luồng (threads). Giao diện này có một phương thức duy nhất là run, mà bạn cần phải triển khai để xác định công việc mà luồng sẽ thực hiện.
↳ Predicate<T>: Predicate trong Java là một giao diện chức năng được sử dụng để kiểm tra điều kiện hoặc tiêu chí cho các đối tượng. Đây là một phần của thư viện java.util.function, được giới thiệu trong Java 8.
Cú Pháp
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
↳ Function<T, R>: là một giao diện chức năng trong Java, nằm trong gói java.util.function, được giới thiệu trong Java 8. Nó đại diện cho một hàm nhận đầu vào kiểu T và trả về giá trị kiểu R.
Cú Pháp
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
↳ Supplier<T>: là một giao diện chức năng trong Java được giới thiệu từ Java 8. Nó là một phần của thư viện java.util.function. Giao diện này đại diện cho một hàm không nhận bất kỳ tham số nào nhưng trả về một giá trị của kiểu T.
Cú Pháp
@FunctionalInterface
public interface Supplier<T> {
T get();
}
↳ Consumer<T>: là một giao diện chức năng trong Java, nằm trong gói java.util.function, được giới thiệu từ Java 8. Nó đại diện cho một hành động nhận một đối tượng kiểu T và không trả về kết quả.
Cú Pháp
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Ⅴ. Ví dụ về giao diện chức năng
Ví dụ 1: Sử dụng biểu thức lambda để in ra một chuỗi:
Ví dụ: Example.java
@FunctionalInterface
interface Runnable {
void run();
}
public class Example {
public static void main(String[] args) {
// Biến ngoài lớp lambda
String message = "Hello, Lambda!";
// Sử dụng biểu thức lambda
Runnable runnable = () -> {
System.out.println(message);
};
// Thực thi phương thức run của Runnable
runnable.run();
}
}
Kết quả của chương trình là:
Biểu Thức
Lambda
Ví dụ này sử dụng giao diện chức năng Runnable với biểu thức lambda để in ra chuỗi "Hello, Lambda!". Biến message được sử dụng trong lambda để xác định nội dung chuỗi cần in ra.
Ví dụ 2: Sử dụng biểu thức lambda không có tham số trong java:
Ví dụ: Example.java
@FunctionalInterface
interface Greeting {
void greet();
}
public class Example {
public static void main(String[] args) {
// Sử dụng biểu thức lambda không có tham số
Greeting g = () -> System.out.println("Hello, world!");
// Gọi phương thức greet của Greeting
g.greet();
}
}
Kết quả của chương trình là:
Ví dụ này sử dụng giao diện chức năng Greeting với biểu thức lambda không có tham số để in ra chuỗi "Hello, world!". Biểu thức lambda () -> System.out.println("Hello, world!") không nhận tham số và thực hiện hành động in ra thông điệp.
Ví dụ 3: Sử dụng biểu thức lambda có 1 tham số trong java:
Ví dụ: Example.java
@FunctionalInterface
interface Greeting {
void greet(String name);
}
public class Example {
public static void main(String[] args) {
// Sử dụng biểu thức lambda với một tham số
Greeting g = name -> System.out.println("Hello, " + name + "!");
// Gọi phương thức greet với tham số
g.greet("Vương");
g.greet("Trường");
}
}
Kết quả của chương trình là:
Hello, Trường!
Ví dụ này sử dụng giao diện chức năng Greeting với biểu thức lambda name -> System.out.println("Hello, " + name + "!"), trong đó name là tham số đầu vào. Biểu thức lambda thực hiện hành động in ra một thông điệp chào với tên được cung cấp.
Ví dụ 4: Sử dụng biểu thức lambda để cộng hai số nguyên trong Java:
Ví dụ: Example.java
public class Example {
public static void main(String[] args) {
// Biểu thức lambda không sử dụng từ khóa return
Calculator add = (a, b) -> a + b;
// Thực hiện phép cộng hai số nguyên và in kết quả
int result = add.calculate(5, 5);
System.out.println("Không sử dụng từ khóa return: " + result);
// Biểu thức lambda có sử dụng từ khóa return
Calculator add1 = (a, b) ->{
return a + b;
};
// Thực hiện phép cộng hai số nguyên và in kết quả
int result1 = add1.calculate(5, 5);
System.out.println("Sử dụng từ khóa return: " + result1);
}
// Định nghĩa một interface với một phương thức trừu tượng
interface Calculator {
int calculate(int a, int b);
}
}
Kết quả của chương trình là:
Sử dụng từ khóa return: 10
Lưu ý:
↳ Không sử dụng return: Dùng khi lambda chỉ có một câu lệnh và không cần khối lệnh. Cú pháp ngắn gọn và tự động trả về giá trị của câu lệnh.
↳ Sử dụng return: Dùng khi lambda có nhiều câu lệnh và cần khối lệnh. Cần chỉ định rõ ràng giá trị trả về với từ khóa return.
Ví dụ 5: Sử dụng biểu thức lambda với vòng lặp for trong Java:
Ví dụ: Example.java
@FunctionalInterface
interface Runnable {
void run();
}
public class Example {
public static void main(String[] args) {
// Biến ngoài lớp lambda
int count = 5;
// Sử dụng biểu thức lambda
Runnable runnable = () -> {
for (int i = 0; i < count; i++) {
System.out.println("Count: " + i);
}
};
// Thực thi phương thức run của Runnable
runnable.run();
}
}
Kết quả của chương trình là:
Count: 1
Count: 2
Count: 3
Count: 4
Ví dụ này tạo ra một giao diện chức năng Runnable với phương thức run(). Biểu thức lambda được sử dụng để cung cấp thực thi cho phương thức run(), và nó in ra các số từ 0 đến 4. Biến count được sử dụng trong lambda để xác định số lần lặp.
Ví dụ 6: Sử dụng biểu thức lambda với vòng lặp foreach trong Java:
Ví dụ: Example.java
import java.util.Arrays;
import java.util.List;
public class Example {
public static void main(String[] args) {
// Tạo danh sách các chuỗi
List<String> words = Arrays.asList("hello", "world", "java");
// Sử dụng biểu thức lambda trong phương thức forEach để in ra từng phần tử
words.forEach(word -> System.out.println(word ));
System.out.println();
// Sử dụng biểu thức lambda trong phương thức forEach để in ra từng từ với chữ hoa
words.forEach(word -> {
String uppercaseWord = word.toUpperCase();
System.out.println(uppercaseWord);
});
}
}
Kết quả của chương trình là:
world
java
HELLO
WORLD
JAVA
Trong ví dụ này, words.forEach(name -> System.out.println(words)) sử dụng biểu thức lambda để lặp qua danh sách và in ra từng chuỗi. words.forEach(word -> { String uppercaseWord = word.toUpperCase(); System.out.println(uppercaseWord); }) sử dụng biểu thức lambda để lặp qua danh sách và in ra từng từ với chữ hoa. forEach và Lambda: Sử dụng biểu thức lambda với forEach giúp thực hiện các hành động trên các phần tử của danh sách một cách dễ dàng và ngắn gọn.
Ví dụ 7: Sử dụng biểu thức lambda với luồng (Thread) trong java:
Ví dụ: Example.java
public class Example {
public static void main(String[] args) {
// Tạo một luồng mới sử dụng biểu thức lambda
Thread thread = new Thread(() -> {
// Hành động của luồng
System.out.println("Hello from a thread!");
});
// Khởi chạy luồng
thread.start();
}
}
Kết quả của chương trình là:
Giải Thích:
Thread thread = new Thread(() -> { System.out.println("Hello from a thread!"); }); sử dụng biểu thức lambda để định nghĩa hành động của luồng, thay vì phải tạo một lớp riêng thực thi Runnable.
thread.start(); khởi chạy luồng để thực hiện hành động đã định nghĩa.
Ví dụ với hành động phức tạp hơn:
Ví dụ: Example.java
public class Example {
public static void main(String[] args) {
// Tạo một luồng mới với hành động phức tạp hơn
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("lặp lại lần " + i + " từ luồng " + Thread.currentThread().getName());
try {
Thread.sleep(500); // Dừng luồng trong 500ms
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// Khởi chạy luồng
thread.start();
}
}
Kết quả của chương trình là:
lặp lại lần 1 từ luồng Thread-0
lặp lại lần 2 từ luồng Thread-0
lặp lại lần 3 từ luồng Thread-0
lặp lại lần 4 từ luồng Thread-0
Giải Thích:
Thread thread = new Thread(() -> { /* code */ }); định nghĩa hành động của luồng với một vòng lặp in ra thông tin và tạm dừng luồng.
Thread.sleep(500); làm cho luồng dừng lại trong 500 mili giây.
thread.start(); khởi chạy luồng và thực hiện hành động đã định nghĩa.
Tổng Kết:
↳ Biểu thức lambda giúp làm cho mã liên quan đến luồng trở nên ngắn gọn và dễ đọc hơn.
↳ Thread và biểu thức lambda kết hợp cho phép bạn dễ dàng định nghĩa hành động của luồng mà không cần phải viết mã cho các lớp phụ.
↳ Việc sử dụng biểu thức lambda với Thread không chỉ giúp mã trở nên ngắn gọn hơn mà còn dễ đọc hơn, đặc biệt khi bạn cần định nghĩa hành động của luồng một cách nhanh chóng và rõ ràng.
Ví dụ 8: Sử dụng biểu thức lambda với Comparator<T>trong Java:
Trong Java, Comparator<T> là một giao diện chức năng được sử dụng để xác định cách so sánh hai đối tượng của cùng một loại. Bạn có thể sử dụng biểu thức lambda để dễ dàng tạo ra các trình so sánh tùy chỉnh cho các đối tượng. Dưới đây là ví dụ đơn giản về cách sử dụng biểu thức lambda với Comparator<T>:
Ví dụ: Example.java
import java.util.Arrays;
import java.util.List;
class Person {
private String name;
private int age;
// Constructor
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getter methods
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return name + " (" + age + " tuổi)";
}
}
public class Example {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Vương", 25),
new Person("Trường", 20),
new Person("Dương", 30)
);
// Sắp xếp theo tuổi (tăng dần) sử dụng biểu thức lambda
people.sort((p1, p2) -> Integer.compare(p1.getAge(), p2.getAge()));
System.out.println("Sắp xếp theo độ tuổi:");
people.forEach(System.out::println);
// Sắp xếp theo tên (tăng dần) sử dụng biểu thức lambda
people.sort((p1, p2) -> p1.getName().compareTo(p2.getName()));
System.out.println("\nSắp xếp theo độ tên:");
people.forEach(System.out::println);
}
}
Kết quả của chương trình là:
Trường (20 tuổi)
Vương (25 tuổi)
Dương (30 tuổi)
Sắp xếp theo độ tên:
Dương (30 tuổi)
Trường (20 tuổi)
Vương (25 tuổi)
Biểu thức Lambda với Comparator<T>: Giúp sắp xếp danh sách đối tượng dễ dàng bằng cách cung cấp một cách đơn giản để so sánh các đối tượng theo các tiêu chí khác nhau như tuổi hoặc tên.
Ví dụ 9: Sử dụng biểu thức lambda trong ứng dụng GUI với Java Swing:
Ví dụ: Example.java
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Example {
public static void main(String[] args) {
// Tạo khung chính
JFrame frame = new JFrame("Lambda GUI Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 200);
// Tạo một nút
JButton button = new JButton("Click Me");
// Thêm hành động cho nút bằng biểu thức lambda
button.addActionListener(e -> System.out.println("Nút đã được nhấp!"));
// Thêm nút vào bảng điều khiển
JPanel panel = new JPanel();
panel.add(button);
// Thêm bảng điều khiển vào khung
frame.add(panel);
// Hiển thị khung
frame.setVisible(true);
frame.setLocationRelativeTo(null);
}
}
Kết quả của chương trình là:

Biểu thức Lambda trong Ứng Dụng GUI: Đơn giản hóa việc xử lý sự kiện bằng cách sử dụng cú pháp ngắn gọn hơn so với các cách truyền thống như lớp nội bộ hoặc lớp ẩn danh.
Ví dụ 10: Sử dụng biểu thức lambda với Predicate trong Java để lọc và in ra danh sách các đối tượng Person theo tiêu chí nhất định
Ví dụ: Example.java
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public void printPerson() {
System.out.println(name + " - " + age);
}
}
public class Example {
public static void main(String[] args) {
List<Person> roster = new ArrayList<>();
roster.add(new Person("Vương", 25));
roster.add(new Person("Trường", 20));
roster.add(new Person("Dương", 30));
System.out.println("Những người có tuổi lớn hơn hoặc bằng 21:");
// In ra những người có tuổi lớn hơn hoặc bằng 21
printPersons(roster, p -> p.getAge() >= 21);
System.out.println("\nNhững người có tuổi nhỏ hơn 21:");
// In ra những người có tuổi nhỏ hơn 21
printPersons(roster, p -> p.getAge() < 21);
}
public static void printPersons(List<Person> roster, Predicate<Person> tester) {
for (Person p : roster) {
if (tester.test(p)) {
p.printPerson();
}
}
}
}
Kết quả của chương trình là:
Vương - 25
Dương - 30
Những người có tuổi nhỏ hơn 21:
Trường - 20
Trong ví dụ này, minh họa cách sử dụng biểu thức lambda để lọc dữ liệu trong danh sách. Biểu thức lambda cung cấp một cách ngắn gọn và linh hoạt để định nghĩa các điều kiện lọc, giúp cải thiện khả năng đọc và viết code.