hocvietcode.com
  • Trang chủ
  • Học lập trình
    • Lập trình C/C++
    • Lập trình HTML
    • Lập trình Javascript
      • Javascript cơ bản
      • ReactJS framework
      • AngularJS framework
      • Typescript cơ bản
      • Angular
    • Lập trình Mobile
      • Lập Trình Dart Cơ Bản
        • Dart Flutter Framework
    • Cơ sở dữ liệu
      • MySQL – MariaDB
      • Micrsoft SQL Server
      • Extensible Markup Language (XML)
      • JSON
    • Lập trình PHP
      • Lập trình PHP cơ bản
      • Laravel Framework
    • Lập trình Java
      • Java Cơ bản
    • Cấu trúc dữ liệu và giải thuật
    • Lập Trình C# Cơ Bản
    • Machine Learning
  • WORDPRESS
    • WordPress cơ bản
    • WordPress nâng cao
    • Chia sẻ WordPress
  • Kiến thức hệ thống
    • Microsoft Azure
    • Docker
    • Linux
  • Chia sẻ IT
    • Tin học văn phòng
      • Microsoft Word
      • Microsoft Excel
    • Marketing
      • Google Adwords
      • Facebook Ads
      • Kiến thức khác
    • Chia sẻ phần mềm
    • Review công nghệ
    • Công cụ – tiện ích
      • Kiểm tra bàn phím online
      • Kiểm tra webcam online
Đăng nhập
  • Đăng nhập / Đăng ký

Please enter key search to display results.

Home
  • Java Cơ bản
Biểu thức lambda nâng cao

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

Lập trình chức năng (functional programing), biểu thức lambda trong Java – Web888 chia sẻ kiến thức lập trình, kinh doanh, mmo

Mục lục

  • Sử Dụng Lambda
    • Biểu Thức Lambda so với Giao Diện Vô Danh (anonymous interface)
    • Các Loại Lambda
    • Cú Pháp Biến Cục Bộ cho Tham Số Lambda
      • Trước Java 11:
      • Trong Java 11 và phiên bản sau đó (sử dụng cú pháp biến cục bộ cho tham số lambda):
    • Giao Diện Chức Năng Dựng Sẵn
      • Giao Diện Chức Năng
    • Phiên Bản Nguyên Thủy của Giao Diện Chức Năng
    • Phiên Bản Nhị Phân của Giao Diện Chức Năng
    • Giao Diện UnaryOperator
  • Tối Ưu Hóa để Tăng Khả Năng Đọc Mã
    • Tối Ưu Hóa Mã Chạy
    • 11.2.2 Tối Ưu Hóa Mã Comparator
      • Ví dụ chưa sử dụng Comparator:
      • Mã viết lại bằng biểu thức lambda
    • Tối Ưu Hóa Mã Đồng Thời
  • Gỡ lỗi trong biểu thức lambdas
    • 11.4 Biểu thức Lambda với Giao diện Chức năng Tự định nghĩa
      • Giao diện với Cả Phương thức Mặc định và Phương thức Tĩnh

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ệnPhương Thức Trừu TượngMô 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).

Bài viết liên quan:

Các tính năng nâng cao mới trong Java
Logging API, Resource Bundles, Networking trong Java
Cấu trúc dữ liệu Java
Design Pattern (Mẫu thiết kế) và các tính năng nâng cao khác
Các tính năng nâng cao của JDBC API
Java JDBC API
Đa luồng (multithreading) và đa nhiệm (Concurrency)
Thread (Luồng) trong Java
Stream (luồng) và xử lý File trong Java
Generic trong Java
Java Utility API, các Collections trong Java
Tìm hiểu thêm Package, regular expression trong Java

THÊM BÌNH LUẬN Cancel reply

Dịch vụ thiết kế Wesbite

NỘI DUNG MỚI CẬP NHẬT

4. KIỂM THỬ VÀ TRIỂN KHAI HỆ THỐNG

2. PHÂN TÍCH VÀ ĐẶC TẢ HỆ THỐNG

3. THIẾT KẾ HỆ THỐNG

1. TỔNG QUAN KIẾN THỨC THỰC HÀNH TRIỂN KHAI DỰ ÁN CÔNG NGHỆ THÔNG TIN

Hướng dẫn tự cài đặt n8n comunity trên CyberPanel, trỏ tên miền

Giới thiệu

hocvietcode.com là website chia sẻ và cập nhật tin tức công nghệ, chia sẻ kiến thức, kỹ năng. Chúng tôi rất cảm ơn và mong muốn nhận được nhiều phản hồi để có thể phục vụ quý bạn đọc tốt hơn !

Liên hệ quảng cáo: [email protected]

Kết nối với HỌC VIẾT CODE

© hocvietcode.com - Tech888 Co .Ltd since 2019

Đăng nhập

Trở thành một phần của cộng đồng của chúng tôi!
Registration complete. Please check your email.
Đăng nhập bằng google
Đăng kýBạn quên mật khẩu?

Create an account

Welcome! Register for an account
The user name or email address is not correct.
Registration confirmation will be emailed to you.
Log in Lost your password?

Reset password

Recover your password
Password reset email has been sent.
The email could not be sent. Possible reason: your host may have disabled the mail function.
A password will be e-mailed to you.
Log in Register
×