Biểu thức lambda nâng cao
- 20-12-2023
- Toanngo92
- 0 Comments
Trước khi đi vào bài viết này, hãy xem lại bài viết dưới nếu bạn chưa nắm bắt được khái niệm về biểu thức lambda
Mục lục
Sử Dụng Lambda
Biểu thức lambda, hay đơn giản là lambda, được giới thiệu trong Java 8 là một khối mã không tên giúp thuận tiện cho lập trình hàm. Lambda giúp đơn giản hóa việc viết mã và truyền mã xung quanh ứng dụng để thực thi sau này.
Biểu Thức Lambda so với Giao Diện Vô Danh (anonymous interface)
Biểu thức lambda thường được sử dụng để biểu diễn các thể hiện của giao diện hàm. Một giao diện hàm là bất kỳ giao diện nào chứa một phương thức trừu tượng duy nhất. Biểu thức lambda thực hiện giao diện hàm bằng cách chỉ triển khai hàm trừu tượng đó. Cú pháp được sử dụng là toán tử lambda ->
thân hàm, trong đó toán tử lambda có thể như sau:
Không có tham số:() -> System.out.println("Biểu thức lambda không tham số");
Một tham số:(p) -> System.out.println("Một tham số: " + p);
Nhiều tham số:(p1, p2) -> System.out.println("Nhiều tham số: " + p1 + ", " + p2);
Việc sử dụng dấu ngoặc đơn không bắt buộc nếu kiểu biến có thể dễ dàng suy luận từ ngữ cảnh.
Có nhiều điểm tương đồng giữa biểu thức lambda và giao diện vô danh. Tuy nhiên, biểu thức lambda hiệu quả hơn, vì chúng có thể nhận các tham số tương tự như các phương thức khác. Số lượng tham số tối thiểu cho cả biểu thức lambda và phương thức phải giống nhau.
Sử Dụng Biểu Thức Lambda trong JavaFX
Thường xuyên, các sự kiện trong ứng dụng Giao Diện Người Dùng Đồ Họa (GUI), như sự kiện chuột, bàn phím và cuộn, đòi hỏi bộ xử lý sự kiện. Để thực hiện điều này, bạn cần triển khai một giao diện cụ thể, đó là các giao diện hàm chỉ có một phương thức.
Trong JavaFX, bạn có thể thay thế lớp vô danh bằng biểu thức lambda như trong đoạn mã:
Đoạn mã 1:
Button btn = new Button();
btn.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("Đây là JavaFX");
}
});
Ở đây, cuộc gọi phương thức btn.setOnAction
chỉ định hành động sẽ xảy ra khi nút được đại diện bởi đối tượng btn
được nhấp. Một đối tượng của loại EventHandler<ActionEvent>
cần thiết cho điều này. Giao diện EventHandler<ActionEvent>
chỉ chứa một phương thức là void handle(ActionEvent event)
. Đây là một giao diện hàm. Do đó, bạn có thể sử dụng biểu thức lambda như sau để thay thế:
btn.setOnAction(event -> System.out.println("Đây là JavaFX"));
Các Loại Lambda
Dựa trên cách biểu thức lambda được viết, lambda có thể được phân loại thành hai loại. Loại cơ bản nhất của lambda là lambda đối tượng. Một lambda đối tượng triển khai một giao diện hàm và được lưu trữ trong một biến để sử dụng sau này. Loại lambda khác là lambda nội suy được truyền nội suy như tham số cho các phương thức.
Bất kỳ cách nào lambda được viết, lambda có thể sử dụng các kiểu nội suy, cho phép truyền các tham số vào cơ thể biểu thức mà không cần chỉ định kiểu tham số. Ngoài ra, cơ thể biểu thức của lambda có thể chứa nhiều câu lệnh và có kiểu trả về.
Đoạn mã dưới tạo ra các loại lambda khác nhau và hướng dẫn cách sử dụng chúng:
import java.util.Arrays;
@FunctionalInterface
interface FunctionalA {
int doWork(int a, int b);
}
public class LambdaExpressionDemo {
public static void main(String[] args) {
/* Lambda 1: Sử dụng cú pháp lambda cơ bản */
FunctionalA functionalA1 = (int num1, int num2) -> num1 + num2;
System.out.println("5 + 5 = " + functionalA1.doWork(5, 5));
/* Lambda 2: Sử dụng lambda với kiểu nội suy */
FunctionalA functionalA2 = (num1, num2) -> num1 + num2;
System.out.println("5 + 10 = " + functionalA2.doWork(5, 10));
/* Lambda 3: Sử dụng lambda với cơ thể biểu thức chứa câu lệnh return */
FunctionalA functionalA3 = (num, num2) -> {
return num + num2;
};
System.out.println("5 + 11 = " + functionalA3.doWork(5, 11));
/* Lambda 4: Sử dụng Lambda với cơ thể biểu thức chứa nhiều câu lệnh */
FunctionalA functionalA4 = (num1, num2) -> {
int sum = num1 + num2;
int result = sum * 10;
return result;
};
System.out.println("(5 + 10) * 10 = " + functionalA4.doWork(5, 10));
/* Lambda 5: Truyền lambda như tham số phương thức cho phương thức Arrays.sort() */
String[] words = new String[] {"Hi", "Hello", "HelloWorld", "#"};
System.out.println("Mảng gốc: " + Arrays.toString(words));
Arrays.sort(words, (first, second) -> Integer.compare(first.length(), second.length()));
System.out.println("Mảng sắp xếp theo độ dài sử dụng lambda: " + Arrays.toString(words));
}
}
Mã tạo một giao diện hàm, tên là FunctionalA
. Sau đó, mã sử dụng nhiều lambda trong lớp LambdaExpressionDemo
dựa trên giao diện hàm FunctionalA
. Các lambda là:
- Lambda 1: Sử dụng cú pháp lambda cơ bản để gán giá trị của biểu thức lambda vào một biến kiểu
FunctionalA
. - Lambda 2: Thực hiện chức năng tương tự như Lambda 1 nhưng không chỉ định kiểu tham số. Trình biên dịch suy luận kiểu bằng cách xem kiểu tham số được khai báo trong phương thức trừu tượng tương ứng của giao diện hàm
FunctionalA
. - Lambda 3: Thực hiện chức năng tương tự như Lambda 1 nhưng có một câu lệnh
return
rõ ràng được bao quanh trong dấu ngoặc nhọn{}
. - Lambda 4: Sử dụng nhiều câu lệnh trong cơ thể biểu thức lambda trước khi trả giá trị cuối cùng cho người gọi.
- Lambda 5: Truyền một lambda làm tham số thứ hai cho phương thức
Arrays.sort()
, phương thức này sắp xếp mảng được chỉ định của đối tượng theo thứ tự được tạo ra bởi bộ so sánh được chỉ định.
Output:
5 + 5 = 10
5 + 10 = 15
5 + 11 = 16
(5 + 10) * 10 = 150
Mảng gốc: [Hi, Hello, HelloWorld, #]
Mảng sắp xếp theo độ dài sử dụng lambda: [#, Hi, Hello, HelloWorld]
Capture của Lambda
Biến được định nghĩa trong phạm vi bên ngoài có thể được sử dụng bởi biểu thức lambda. Những lambda như vậy được gọi là lambda capturing. Chúng có khả năng capture các biến khác nhau như biến static, local hoặc instance. Tuy nhiên, biến local phải là final hoặc effectively final. Ví dụ, xem xét đoạn mã sau:
Supplier<Integer> incrementer(int start) {
return () -> start;
}
Đoạn mã này sẽ không biên dịch vì start
là một biến local và nó đang được sửa đổi bên trong biểu thức lambda. Lý do không biên dịch là biểu thức lambda capture giá trị của start
, tức là tạo một bản sao của nó. Điều này xảy ra khi lambda được trả về từ phương thức. Cho đến khi tham số phương thức start
được thu gom bởi garbage collector, lambda sẽ không chạy. Đây là lý do tại sao Java sao chép start
và do đó, lambda có thể tồn tại bên ngoài phương thức. Vì Java sử dụng ràng buộc effectively final, các vấn đề về đồng thời không phát sinh. Tuy nhiên, việc cẩn thận trong môi trường đa luồng là tốt. Nên tránh việc sửa đổi giá trị của biến local. Điều này có thể được đạt được bằng cách không sử dụng trữ giá trị biến.
Cú Pháp Biến Cục Bộ cho Tham Số Lambda
Biểu thức lambda tương tự như các hàm và chúng nhận tham số giống như cách các hàm thực hiện.
Việc sử dụng lớp vô danh không phải lúc nào cũng là lựa chọn lý tưởng khi triển khai của nó rất đơn giản. Xem xét một giao diện chỉ chứa một phương thức duy nhất, trong đó một chức năng phải được truyền dưới dạng đối số cho một phương thức khác. Ví dụ, loại hành động sẽ xảy ra sau khi người dùng nhấp vào một nút. Trong những trường hợp như vậy, việc sử dụng lớp vô danh trở nên rối bời. Thay vào đó, bằng cách sử dụng biểu thức lambda, chức năng này có thể được xem xét như một đối số phương thức hoặc được mã hóa dưới dạng dữ liệu. Mặc dù việc triển khai một lớp cơ sở không có tên thường ngắn gọn hơn việc sử dụng một lớp có tên, nhưng vẫn tốt hơn nếu sử dụng biểu thức lambda, vì chúng giúp biểu diễn các trường hợp lớp chỉ có một phương thức một cách ngắn gọn hơn.
Từ Java 10 trở đi, có thể sử dụng kiểu ngụ ý cho biến cục bộ. Tuy nhiên, từ trước Java 11, không thể sử dụng từ khóa var
trong khi khai báo danh sách tham số của biểu thức lambda được ngụ ý kiểu. Điều này thay đổi từ Java 11 trở đi và bạn có thể sử dụng từ khóa var
, tương tự như cú pháp biến cục bộ, như được thể hiện ở đây:
Operation divide = (var x, var y) -> x / y;
Do đó, từ khóa var
được phép sử dụng với các tham số hình thức của biểu thức lambda được ngụ ý kiểu. Một ví dụ khác như sau:
(var p, var q) -> System.out.println(p + q);
Ở đây, thay vì chỉ định rõ kiểu của tham số như String
cho biến p
và q
, var
được sử dụng để giúp rút ngắn mã và làm cho nó dễ đọc hơn và cũng tạo ra tính nhất quán với biến cục bộ.
Ngoài việc có cú pháp đồng nhất cho cả biến cục bộ và tham số lambda, một lợi ích quan trọng khác là bây giờ có thể áp dụng các modifier và annotations cho chúng, đồng thời vẫn giữ mã nguồn gọn nhẹ.
Xét các đoạn mã sau:
Trước Java 11:
@MyAnnotation VeryLongClassName x, VeryLongClassName y) -> p.print(q)
Trong Java 11 và phiên bản sau đó (sử dụng cú pháp biến cục bộ cho tham số lambda):
@MyAnnotation var x, var y) -> p.print(q)
Trong phiên bản Java trước 11, cú pháp sử dụng cho tham số lambda là VeryLongClassName x, VeryLongClassName y
, trong đó @MyAnnotation
là một chú thích tùy chỉnh được áp dụng cho một thể hiện x
của lớp VeryLongClassName
. Tuy nhiên, từ Java 11 trở đi, cú pháp biến cụ bộ cho tham số lambda (var x, var y
) đã được sử dụng, giúp làm cho mã nguồn trở nên đơn giản và dễ đọc hơn.
Điều này thể hiện sự cải tiến trong cú pháp của Java, giúp làm cho mã nguồn trở nên ngắn gọn và hiểu quả hơn.
Giao Diện Chức Năng Dựng Sẵn
Java 8 trở đi bao gồm một số lượng lớn gói giao diện chức năng tích hợp sẵn trong java.util.function
.
Giao Diện Chức Năng
Giao Diện | Phương Thức Trừu Tượng | Mô Tả |
---|---|---|
Predicate<T> | boolean test(T t) | Đại diện cho một hoạt động kiểm tra điều kiện và trả về một giá trị boolean là kết quả. |
Consumer<T> | void accept(T t) | Đại diện cho một hoạt động nhận một đối số nhưng không trả về gì. |
Function<T, R> | R apply(T t) | Đại diện cho một hoạt động nhận một đối số và trả về một kết quả cho người gọi. |
Supplier<T> | T get() | Đại diện cho một hoạt động không nhận bất kỳ đối số nào nhưng trả về một giá trị cho người gọi. |
Đoạn mã dưới mô tả cách sử dụng các giao diện chức năng dựng sẵn.
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class FunctionalInterfacesDemo {
static void testPredicate() {
Predicate<String> result = arg -> (arg.equals("Hello Lambda"));
String testStr = "Hello Lambda";
System.out.println(result.test(testStr));
}
static void testConsumer() {
Consumer<String> result = str -> System.out.println(str.toUpperCase());
result.accept("Hello Lambda");
}
static void testFunction() {
Function<String, Integer> result = str -> str.length();
System.out.println(result.apply("Hello Lambda"));
}
static void testSupplier() {
Supplier<String> result = () -> "Hello Lambda from supplier";
System.out.println(result.get());
}
public static void main(String[] args) {
testPredicate();
testConsumer();
testFunction();
testSupplier();
}
}
Phương thức testPredicate()
tạo một lambda làm hiện thực của phương thức boolean test(T t)
của giao diện chức năng Predicate
. Lambda kiểm tra xem tham số được chuyển có phải là “Hello Lambda” và trả về một giá trị Boolean tương ứng. Tiếp theo, phương thức testConsumer()
tạo một lambda làm hiện thực của phương thức void accept(T t)
của giao diện chức năng Consumer
. Lambda này nhận một chuỗi làm tham số, chuyển đổi chuỗi thành chữ in hoa và in ra màn hình console. Phương thức testFunction()
tạo một lambda làm hiện thực của phương thức R apply(T t)
của giao diện chức năng Function
. Lambda này nhận một chuỗi, tính độ dài của nó và trả về độ dài tính toán dưới dạng giá trị int. Cuối cùng, phương thức testSupplier()
sử dụng lambda cho phương thức T get()
của giao diện chức năng Supplier
. Lambda này đơn giản trả về một chuỗi cho người gọi.
Output:
true
HELLO LAMBDA
11
Hello Lambda from supplier
Phiên Bản Nguyên Thủy của Giao Diện Chức Năng
Các giao diện chức năng Predicate<T>
, Consumer<T>
, Function<T, R>
, và Supplier<T>
là generic và do đó hoạt động trên các đối tượng kiểu tham chiếu. Giá trị nguyên thủy như int, long, float, hoặc double không thể được sử dụng với chúng. Do đó, Java cung cấp các phiên bản nguyên thủy cho các giao diện chức năng này. Ví dụ, IntPredicate
, LongPredicate
, và DoublePredicate
là các phiên bản nguyên thủy của giao diện Predicate
. Tương tự, IntConsumer
, LongConsumer
, và DoubleConsumer
là các phiên bản nguyên thủy của giao diện Consumer
.
Đoạn Mã dưới thể hiện cách sử dụng phiên bản nguyên thủy của các giao diện chức năng Predicate và Consumer.
import java.util.function.IntPredicate;
import java.util.function.LongConsumer;
public class PrimitiveFunctionalInterfacesDemo {
static void testIntPredicate() {
IntPredicate result = arg -> (arg == 10);
System.out.println("IntPredicate.test() result: " + result.test(11));
}
static void testLongConsumer() {
LongConsumer result = val -> System.out.println("LongConsumer.accept() result: " + val * val);
result.accept(1000000);
}
public static void main(String[] args) {
testIntPredicate();
testLongConsumer();
}
}
Trong đoạn mã này, chúng ta sử dụng phiên bản nguyên thủy IntPredicate
của giao diện Predicate
trong phương thức testIntPredicate()
để kiểm tra xem tham số int được cung cấp có bằng 10 không. Phiên bản nguyên thủy LongConsumer
của giao diện Consumer
thì bình phương giá trị long được cung cấp trong phương thức testLongConsumer()
.
Output:
IntPredicate.test() result: false
LongConsumer.accept() result: 1000000000000
Phiên Bản Nhị Phân của Giao Diện Chức Năng
Các phương thức trừu tượng của các giao diện chức năng Predicate, Consumer, và Function chấp nhận một đối số. Java cung cấp các phiên bản nhị phân tương đương của các giao diện chức năng này có thể chấp nhận hai tham số.
Các phiên bản chức năng nhị phân được tiền tố bằng Bi, ví dụ như BiPredicate, BiConsumer, và Bifunction.
Lưu ý: Vì phương thức trừu tượng T get() của Supplier không chấp nhận bất kỳ đối số nào, nên không có phiên bản nhị phân tương ứng của Supplier.
Ví dụ:
import java.util.function.BiPredicate;
import java.util.function.BiConsumer;
public class BinaryFunctionalInterfacesDemo {
static void testBiPredicate() {
BiPredicate<Integer, Integer> result = (arg1, arg2) -> arg1 < arg2;
System.out.println("BiPredicate.test() result: " + result.test(5, 10));
}
static void testBiConsumer() {
BiConsumer<String, String> result = (arg1, arg2) ->
System.out.println("BiConsumer.accept() result: " + arg1 + arg2);
result.accept("Hello", "Lambda");
}
public static void main(String[] args) {
testBiPredicate();
testBiConsumer();
}
}
Trong đoạn mã này, chúng ta sử dụng phiên bản nhị phân BiPredicate
của giao diện Predicate
trong phương thức testBiPredicate()
để kiểm tra xem tham số số nguyên đầu tiên có nhỏ hơn tham số thứ hai không. Trong phương thức testBiConsumer()
, phiên bản nguyên thủy BiConsumer
của giao diện Consumer
thêm và in ra hai tham số chuỗi.
Output:
BiPredicate.test() result: true
BiConsumer.accept() result: HelloLambda
Giao Diện UnaryOperator
Gói java.util.function
chứa một giao diện chức năng UnaryOperator
là một phiên bản chuyên biệt của giao diện Function
. UnaryOperator
có thể được sử dụng trên một toán hạng khi kiểu của toán hạng và kết quả là giống nhau.
Ví dụ:
import java.util.function.UnaryOperator;
public class UnaryOperatorDemo {
public static void main(String[] args) {
UnaryOperator<String> result = (x) -> x.toUpperCase();
System.out.println("Output converted into uppercase:");
System.out.println(result.apply("Hello Lambda"));
}
}
Đoạn mã này sử dụng một UnaryOperator
làm mục tiêu gán cho một lambda. Biểu thức lambda nhận một tham số chuỗi, chuyển đổi nó thành chữ in hoa và trả kết quả dưới dạng chuỗi. Phương thức apply()
mà UnaryOperator
kế thừa từ Function
thực hiện lambda.
Output:
Output converted into uppercase:
HELLO LAMBDA
Tối Ưu Hóa để Tăng Khả Năng Đọc Mã
Lambda có thể tăng đáng kể khả năng đọc của mã. Người lập trình Java có thể sử dụng lambda để diễn đạt vấn đề trong nhiều tình huống một cách ngắn gọn và dễ đọc hơn so với trước đây. Sự giới thiệu của lambda không làm hỏng mã. Mã hiện tại có thể chạy như thường với mã mới chứa lambda chạy song song. Tuy nhiên, các nhà phát triển có thể muốn tối ưu hóa mã hiện tại của họ để sử dụng lambda thuận tiện hơn. Thông thường, quá trình tối ưu hóa sẽ được thực hiện để loại bỏ mã lặp hiện tại và làm cho mã hiện tại ngắn gọn hơn.
Tối Ưu Hóa Mã Chạy
Trong ứng dụng đa luồng, một cách để triển khai một luồng mới là tạo một đối tượng Runnable
và gọi phương thức run()
của nó. Trước Java 8, điều này được thực hiện thông qua một lớp vô danh.
Ví dụ:
public class MultithreadedAnonymousDemo {
public static void main(String[] args) {
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello from anonymous");
}
};
r1.run();
}
}
Runnable
chứa một phương thức trừu tượng duy nhất là void run()
, nên nó được xem như một giao diện chức năng. Để tăng khả năng đọc, có thể sử dụng lambda để đại diện cho mã cần thực thi. Lambda sau đó có thể được gán cho một biến Runnable
để thực hiện sau này thông qua một cuộc gọi đến phương thức run()
.
Ví dụ viết lại:
public class MultithreadedLambdaDemo {
public static void main(String[] args) {
Runnable r1 = () -> System.out.println("Hello from lambda");
r1.run();
}
}
Trong đoạn mã này, chúng ta sử dụng lambda để biểu diễn một biến Runnable
và chạy nó.
11.2.2 Tối Ưu Hóa Mã Comparator
Giao diện Comparator
cho phép so sánh các phần tử của một bộ sưu tập cần được sắp xếp. Comparator
là một giao diện chức năng chứa một phương thức int compare(T o1, T o2)
. Khi một bộ sưu tập hoặc mảng cần được sắp xếp, một đối tượng Comparator
được chuyển đến phương thức Collections.sort()
hoặc Arrays.sort()
. Trước Java 8, một Comparator
có thể được chuyển đến phương thức sort()
dưới dạng một lớp vô danh.
Ví dụ chưa sử dụng Comparator:
Collections.sort(employeeList, new Comparator<Employee>() {
@Override
public int compare(Employee emp1, Employee emp2) {
return emp1.getLastName().compareTo(emp2.getLastName());
}
});
System.out.println("=== Sorted Employees by last name in ascending order ===");
for (Employee emp : employeeList) {
System.out.println(emp.getFirstName() + " " + emp.getLastName());
}
Đoạn mã này áp dụng một Comparator
sử dụng một lớp inner class vô danh để sắp xếp một danh sách các đối tượng Employee
.
Để tăng khả năng đọc, bạn có thể sử dụng một lambda thay vì lớp inner class để truyền mã so sánh vào phương thức sort()
. Đoạn Mã Snippet 10 liệt kê mã đầy đủ áp dụng một Comparator
bằng cách sử dụng một lambda để sắp xếp một danh sách các đối tượng nhân viên.
Mã viết lại bằng biểu thức lambda
Collections.sort(employeeList, (emp1, emp2) -> emp1.getLastName().compareTo(emp2.getLastName()));
System.out.println("=== Sorted Employees by last name in ascending order ===");
for (Employee emp : employeeList) {
System.out.println(emp.getFirstName() + " " + emp.getLastName());
}
Trong đoạn mã này, chúng ta sử dụng lambda để biểu diễn một Comparator
và sắp xếp danh sách đối tượng nhân viên.
Tối Ưu Hóa Mã Đồng Thời
Callable
và Future
được sử dụng rộng rãi trong các ứng dụng Java đa luồng để thực hiện xử lý bất đồng bộ. Callable
tương tự như Runnable
trong việc cả hai đều có thể được sử dụng để tạo một nhiệm vụ có thể được thực thi bởi các luồng song song. Tuy nhiên, khác với Runnable
không thể trả giá trị, Callable
có thể trả về một giá trị. Ngoài ra, phương thức call()
của Callable
có thể ném một ngoại lệ kiểm tra, điều này không thể xảy ra với phương thức run()
của Runnable
.
Callable
là một giao diện chức năng trong gói java.util.concurrent
. Giao diện Callable
có một phương thức trừu tượng call()
. Khi một Callable
được chuyển đến một hồ bơi luồng do ExecutorService
duy trì, hồ bơi chọn một luồng và thực hiện Callable
. Khi thực thi, luồng trả về một đối tượng Future
giữ kết quả của phép tính sau khi hoàn thành. Phương thức get()
của Future
trả về kết quả tính toán hoặc chặn nếu phép tính chưa hoàn thành.
Ví dụ 1:
ExecutorService executor = Executors.newFixedThreadPool(5);
Callable<String> callable = new Callable<String>() {
@Override
public String call() {
try {
Thread.sleep(10);
return Thread.currentThread().getName();
} catch (InterruptedException ie) {
ie.printStackTrace();
}
return Thread.currentThread().getName();
}
};
Future<String> future = executor.submit(callable);
Ví dụ 2:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableLambdaDemo {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
List<Future<String>> list = new ArrayList<>();
Callable<String> callable = () -> {
try {
Thread.sleep(10);
return Thread.currentThread().getName();
} catch (InterruptedException ie) {
ie.printStackTrace();
}
return Thread.currentThread().getName();
};
for (int i = 0; i < 10; i++) {
Future<String> future = executor.submit(callable);
list.add(future);
}
for (Future<String> future : list) {
try {
System.out.println(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
executor.shutdown();
}
}
Trong đoạn mã ví dụ 1, chúng ta sử dụng một lớp vô danh để chạy một đoạn mã trong một luồng khác với Callable
và Future
. Đoạn mã ví dụ 2 tạo ra một Callable
bằng cách sử dụng lambda thay vì sử dụng lớp vô danh và gửi Callable
đó đến một ExecutorService
.
Output:
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-5
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-5
Gỡ lỗi trong biểu thức lambdas
Code mẫu:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Employee {
private String firstName;
private String lastName;
public Employee(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
public class ComparatorLambdaDemo {
public static void main(String[] args) {
List<Employee> employeeList = new ArrayList<>();
employeeList.add(new Employee("Patrick", "Samuel"));
employeeList.add(new Employee("John", "Doe"));
employeeList.add(new Employee("Andy", "Davidson"));
Comparator<Employee> sortedEmployee = (Employee emp1, Employee emp2) ->
emp1.getLastName().compareTo(emp2.getLastName());
System.out.println("Sorted Employee by last name in ascending order");
Collections.sort(employeeList, sortedEmployee);
for (Employee emp : employeeList) {
System.out.println(emp.getFirstName() + " " + emp.getLastName());
}
}
}
Trong đoạn mã Snippet 13, chúng ta sử dụng một lambda để so sánh hai đối tượng Employee
dựa trên trường lastName
và lưu nó trong một biến Comparator
.
Để kiểm tra lambda được sử dụng trong mã:
- Mở class ComparatorLambdaDemo trong NetBeans.
- Trong trình soạn thảo mã, nhấp đúp vào số dòng của câu lệnh sử dụng lambda để đặt một điểm dừng.
- Trong trình soạn thảo mã, nhấp đúp vào số dòng của câu lệnh chứa vòng lặp for để đặt một điểm dừng (breakpoint).
- Chọn Debug > Debug Project từ menu chính của NetBeans. Chương trình sẽ dừng lại tại điểm dừng đầu tiên.
- Quan sát giá trị tên và họ trong cửa sổ Biến hiển thị. Tại điểm này, lambda vẫn chưa thực hiện việc sắp xếp.
- Chọn Debug > Continue từ menu chính để tiếp tục gỡ lỗi cho đến khi luồng gỡ lỗi đến điểm dừng thứ hai.
- Kiểm tra cửa sổ Biến để đảm bảo rằng lambda đã thực hiện sắp xếp đúng dựa trên họ (last name).
- Chọn Debug -> Finish Debugger Session để dừng quá trình gỡ lỗi
11.4 Biểu thức Lambda với Giao diện Chức năng Tự định nghĩa
Biểu thức Lambda cũng có thể được sử dụng để triển khai một đối tượng chứa giao diện chức năng. Biểu thức Lambda cung cấp các chức năng như tạo một hàm không thuộc bất kỳ lớp nào và truyền biểu thức Lambda xung quanh dưới dạng một đối tượng và thực thi nó khi cần. Đoạn Mã dưới mô tả biểu thức Lambda để triển khai một giao diện chức năng tự định nghĩa.
// Giao diện chức năng tự định nghĩa
interface FuncInterface {
// Một phương thức trừu tượng
void abstractFun(int x);
// Phương thức không trừu tượng (hoặc mặc định)
default void normalFun() {
System.out.println("Hello from defined functional interface");
}
}
public class Test {
public static void main(String args[]) {
// Biểu thức Lambda để triển khai giao diện chức năng tự định nghĩa
// tạo trước đó. Giao diện này, theo mặc định, triển khai abstractFun()
FuncInterface fobj = (int x) -> System.out.println(3 * x);
// Dòng này gọi biểu thức Lambda và in ra 18.
fobj.abstractFun(6);
}
}
Mặc dù đây không phải là một ví dụ thực tế hữu ích cho các kịch bản thực tế, nó phản ánh cách biểu thức Lambda có thể được sử dụng với các giao diện chức năng tự định nghĩa.
Trong ứng dụng GUI, việc sử dụng bộ lắng sự kiện đã trở nên phổ biến, làm cho lập trình hàm trở nên có thể sử dụng hơn. Sử dụng một phương thức duy nhất, một bộ lắng sự kiện có thể được triển khai dưới dạng một giao diện Java. Hãy xem ví dụ:
public interface MouseHover {
public void onMouseIn(State oldState, State newState);
}
Một giao diện chứa một phương thức duy nhất đôi khi được gọi là một giao diện chức năng. Do đó, trở nên dễ dàng để kết hợp một giao diện chức năng với một biểu thức Lambda Java.
Giao diện với Cả Phương thức Mặc định và Phương thức Tĩnh
Từ Java 8 trở đi, có thể có một giao diện với cả phương thức mặc định và phương thức tĩnh. Cả hai đều có thể được triển khai trực tiếp khi khai báo giao diện. Do đó, một biểu thức Lambda Java có thể triển khai giao diện có nhiều hơn một phương thức, miễn là giao diện chỉ có một phương thức chưa triển khai (tức là trừu tượng).