JavaRush /Blog Java /Random-VI /Biểu thức Lambda kèm ví dụ

Biểu thức Lambda kèm ví dụ

Xuất bản trong nhóm
Java ban đầu là một ngôn ngữ hướng đối tượng hoàn toàn. Ngoại trừ các kiểu nguyên thủy, mọi thứ trong Java đều là một đối tượng. Ngay cả mảng cũng là đối tượng. Các thể hiện của mỗi lớp là các đối tượng. Không có một khả năng nào để định nghĩa một hàm riêng biệt (bên ngoài một lớp - xấp xỉ bản dịch ). Và không có cách nào để truyền một phương thức làm đối số hoặc trả về phần thân phương thức dưới dạng kết quả của một phương thức khác. Nó giống như vậy. Nhưng điều này xảy ra trước Java 8. Biểu thức Lambda kèm ví dụ - 1Kể từ thời Swing cũ, cần phải viết các lớp ẩn danh khi cần chuyển một số chức năng cho một số phương thức. Ví dụ: việc thêm một trình xử lý sự kiện sẽ trông như thế này:
someObject.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {

                //Event listener implementation goes here...

            }
        });
Ở đây chúng tôi muốn thêm một số mã vào trình nghe sự kiện chuột. Chúng tôi đã xác định một lớp ẩn danh MouseAdaptervà ngay lập tức tạo một đối tượng từ nó. Bằng cách này, chúng tôi đã chuyển chức năng bổ sung vào addMouseListener. Nói tóm lại, không dễ để truyền một phương thức (chức năng) đơn giản trong Java thông qua các đối số. Hạn chế này buộc các nhà phát triển Java 8 phải thêm một tính năng như biểu thức Lambda vào đặc tả ngôn ngữ.

Tại sao Java cần biểu thức Lambda?

Ngay từ đầu, ngôn ngữ Java đã không phát triển nhiều, ngoại trừ những thứ như Chú thích, Generics, v.v. Trước hết, Java luôn duy trì tính hướng đối tượng. Sau khi làm việc với các ngôn ngữ chức năng như JavaScript, người ta có thể hiểu Java hướng đối tượng chặt chẽ và được gõ mạnh như thế nào. Các hàm không cần thiết trong Java. Bản thân chúng không thể được tìm thấy trong thế giới Java. Trong các ngôn ngữ lập trình chức năng, các chức năng trở nên nổi bật. Chúng tồn tại một mình. Bạn có thể gán chúng cho các biến và chuyển chúng qua các đối số cho các hàm khác. JavaScript là một trong những ví dụ tốt nhất về ngôn ngữ lập trình chức năng. Bạn có thể tìm thấy các bài viết hay trên Internet trình bày chi tiết về lợi ích của JavaScript như một ngôn ngữ chức năng. Các ngôn ngữ chức năng có các công cụ mạnh mẽ như Closure, cung cấp một số lợi thế so với các phương pháp viết ứng dụng truyền thống. Bao đóng là một hàm có môi trường gắn liền với nó - một bảng lưu trữ các tham chiếu đến tất cả các biến không cục bộ của hàm. Trong Java, các bao đóng có thể được mô phỏng thông qua các biểu thức Lambda. Tất nhiên, có sự khác biệt giữa các bao đóng và biểu thức Lambda, không hề nhỏ, nhưng các biểu thức lambda là một sự thay thế tốt cho các bao đóng. Trong blog đầy châm biếm và hài hước của mình, Steve Yegge mô tả cách thế giới Java gắn chặt với các danh từ (thực thể, đối tượng - khoảng. dịch. ). Nếu bạn chưa đọc blog của anh ấy, tôi khuyên bạn nên đọc nó. Anh ấy mô tả một cách hài hước và thú vị lý do chính xác tại sao các biểu thức Lambda được thêm vào Java. Biểu thức Lambda mang lại chức năng cho Java vốn đã bị thiếu từ lâu. Biểu thức Lambda mang lại chức năng cho ngôn ngữ giống như các đối tượng. Mặc dù điều này không đúng 100% nhưng bạn có thể thấy rằng các biểu thức Lambda, mặc dù không phải là dạng đóng, nhưng cũng cung cấp các khả năng tương tự. Trong ngôn ngữ hàm, biểu thức lambda là hàm; nhưng trong Java, biểu thức lambda được biểu diễn bằng các đối tượng và phải được liên kết với một loại đối tượng cụ thể được gọi là giao diện chức năng. Tiếp theo chúng ta sẽ xem nó là gì. Bài viết “Tại sao chúng ta cần Biểu thức Lambda trong Java” của Mario Fusco mô tả chi tiết lý do tại sao tất cả các ngôn ngữ hiện đại đều cần khả năng đóng.

Giới thiệu về Biểu thức Lambda

Biểu thức Lambda là các hàm ẩn danh (có thể không phải là định nghĩa chính xác 100% cho Java, nhưng nó mang lại sự rõ ràng nhất định). Nói một cách đơn giản, đây là một phương thức không cần khai báo, tức là. không có công cụ sửa đổi truy cập, giá trị trả về và tên. Tóm lại, chúng cho phép bạn viết một phương thức và sử dụng nó ngay lập tức. Nó đặc biệt hữu ích trong trường hợp gọi phương thức một lần, bởi vì giảm thời gian khai báo và viết một phương thức mà không cần phải tạo lớp. Biểu thức Lambda trong Java thường có cú pháp sau (аргументы) -> (тело). Ví dụ:
(арг1, арг2...) -> { тело }

(тип1 арг1, тип2 арг2...) -> { тело }
Dưới đây là một số ví dụ về biểu thức Lambda thực:
(int a, int b) -> {  return a + b; }

() -> System.out.println("Hello World");

(String s) -> { System.out.println(s); }

() -> 42

() -> { return 3.1415 };

Cấu trúc của biểu thức Lambda

Hãy cùng nghiên cứu cấu trúc của biểu thức lambda:
  • Biểu thức Lambda có thể có 0 hoặc nhiều tham số đầu vào.
  • Loại tham số có thể được chỉ định rõ ràng hoặc có thể được lấy từ ngữ cảnh. Ví dụ ( int a) có thể viết như sau ( a)
  • Các tham số được đặt trong dấu ngoặc đơn và cách nhau bằng dấu phẩy. Ví dụ ( a, b) hoặc ( int a, int b) hoặc ( String a, int b, float c)
  • Nếu không có tham số thì bạn cần sử dụng dấu ngoặc đơn trống. Ví dụ() -> 42
  • Khi chỉ có một tham số, nếu loại không được chỉ định rõ ràng thì có thể bỏ qua dấu ngoặc đơn. Ví dụ:a -> return a*a
  • Phần thân của biểu thức Lambda có thể chứa 0 hoặc nhiều biểu thức.
  • Nếu phần thân bao gồm một câu lệnh đơn, nó có thể không được đặt trong dấu ngoặc nhọn và giá trị trả về có thể được chỉ định mà không cần từ khóa return.
  • Nếu không, bắt buộc phải có dấu ngoặc nhọn (khối mã) và giá trị trả về phải được chỉ định ở cuối bằng từ khóa return(nếu không kiểu trả về sẽ là void).

Giao diện chức năng là gì

Trong Java, giao diện Marker là các giao diện không khai báo các phương thức hoặc trường. Nói cách khác, giao diện mã thông báo là giao diện trống. Tương tự, Giao diện chức năng là các giao diện chỉ có một phương thức trừu tượng được khai báo trên đó. java.lang.Runnablelà một ví dụ về giao diện chức năng. Nó chỉ khai báo một phương thức void run(). Ngoài ra còn có một giao diện ActionListener- cũng có chức năng. Trước đây, chúng tôi phải sử dụng các lớp ẩn danh để tạo các đối tượng triển khai giao diện chức năng. Với biểu thức Lambda, mọi thứ đã trở nên đơn giản hơn. Mỗi biểu thức lambda có thể được liên kết ngầm với một số giao diện chức năng. Ví dụ: bạn có thể tạo một tham chiếu đến Runnablemột giao diện, như trong ví dụ sau:
Runnable r = () -> System.out.println("hello world");
Kiểu chuyển đổi này luôn được thực hiện ngầm khi chúng tôi không chỉ định giao diện chức năng:
new Thread(
    () -> System.out.println("hello world")
).start();
Trong ví dụ trên, trình biên dịch tự động tạo biểu thức lambda dưới dạng triển khai Runnablegiao diện từ hàm tạo của lớp Thread: public Thread(Runnable r) { }. Dưới đây là một số ví dụ về biểu thức lambda và giao diện chức năng tương ứng:
Consumer<Integer> c = (int x) -> { System.out.println(x) };

BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);

Predicate<String> p = (String s) -> { s == null };
Chú thích @FunctionalInterfaceđược thêm vào Java 8 theo Đặc tả ngôn ngữ Java sẽ kiểm tra xem giao diện được khai báo có hoạt động hay không. Ngoài ra, Java 8 còn bao gồm một số giao diện chức năng được tạo sẵn để sử dụng với biểu thức Lambda. @FunctionalInterfacesẽ đưa ra lỗi biên dịch nếu giao diện được khai báo không hoạt động. Sau đây là một ví dụ về việc xác định một giao diện chức năng:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
Như định nghĩa gợi ý, một giao diện chức năng chỉ có thể có một phương thức trừu tượng. Nếu bạn cố gắng thêm một phương thức trừu tượng khác, bạn sẽ gặp lỗi biên dịch. Ví dụ:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

    public void doSomeMoreWork();

}
Lỗi
Unexpected @FunctionalInterface annotation
    @FunctionalInterface ^ WorkerInterface is not a functional interface multiple
    non-overriding abstract methods found in interface WorkerInterface 1 error
После определения функционального интерфейса, мы можем его использовать и получать все преимущества Lambda-выражений. Пример:// defining a functional interface
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
public class WorkerInterfaceTest {

    public static void execute(WorkerInterface worker) {
        worker.doSomeWork();
    }

    public static void main(String [] args) {

      // calling the doSomeWork method via an anonymous class
      // (classic)
      execute(new WorkerInterface() {
            @Override
            public void doSomeWork() {
               System.out.println("Worker called via an anonymous class");
            }
        });

      // calling the doSomeWork method via Lambda expressions
      // (Java 8 new)
      execute( () -> System.out.println("Worker called via Lambda") );
    }

}
Phần kết luận:
Worker вызван через анонимный класс
Worker вызван через Lambda
Ở đây chúng tôi đã xác định giao diện chức năng của riêng mình và sử dụng biểu thức lambda. Phương thức này execute()có khả năng chấp nhận các biểu thức lambda làm đối số.

Ví dụ về biểu thức Lambda

Cách tốt nhất để hiểu biểu thức Lambda là xem một số ví dụ: Một luồng Threadcó thể được khởi tạo theo hai cách:
// Old way:
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from thread");
    }
}).start();
// New way:
new Thread(
    () -> System.out.println("Hello from thread")
).start();
Quản lý sự kiện trong Java 8 cũng có thể được thực hiện thông qua biểu thức Lambda. Sau đây là hai cách để thêm trình xử lý sự kiện ActionListenervào thành phần giao diện người dùng:
// Old way:
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button pressed. Old way!");
    }
});
// New way:
button.addActionListener( (e) -> {
        System.out.println("Button pressed. Lambda!");
});
Một ví dụ đơn giản về hiển thị tất cả các phần tử của một mảng nhất định. Lưu ý rằng có nhiều cách để sử dụng biểu thức lambda. Dưới đây, chúng tôi tạo biểu thức lambda theo cách thông thường bằng cách sử dụng cú pháp mũi tên và chúng tôi cũng sử dụng toán tử dấu hai chấm (::), trong Java 8 chuyển đổi một phương thức thông thường thành biểu thức lambda:
// Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
    System.out.println(n);
}
// New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
// New way using double colon operator ::
list.forEach(System.out::println);
Trong ví dụ sau, chúng tôi sử dụng giao diện chức năng Predicateđể tạo bài kiểm tra và in các mục vượt qua bài kiểm tra đó. Bằng cách này, bạn có thể đưa logic vào biểu thức lambda và thực hiện mọi việc dựa trên nó.
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Main {

    public static void main(String [] a)  {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

        System.out.print("Outputs all numbers: ");
        evaluate(list, (n)->true);

        System.out.print("Does not output any number: ");
        evaluate(list, (n)->false);

        System.out.print("Output even numbers: ");
        evaluate(list, (n)-> n%2 == 0 );

        System.out.print("Output odd numbers: ");
        evaluate(list, (n)-> n%2 == 1 );

        System.out.print("Output numbers greater than 5: ");
        evaluate(list, (n)-> n > 5 );

    }

    public static void evaluate(List<Integer> list, Predicate<Integer> predicate) {
        for(Integer n: list)  {
            if(predicate.test(n)) {
                System.out.print(n + " ");
            }
        }
        System.out.println();
    }

}
Phần kết luận:
Выводит все числа: 1 2 3 4 5 6 7
Не выводит ни одного числа:
Вывод четных чисел: 2 4 6
Вывод нечетных чисел: 1 3 5 7
Вывод чисел больше 5: 6 7
Bằng cách mày mò các biểu thức Lambda, bạn có thể hiển thị bình phương của từng phần tử trong danh sách. Lưu ý rằng chúng tôi đang sử dụng phương thức này stream()để chuyển đổi danh sách thông thường thành luồng. Java 8 cung cấp một lớp tuyệt vời Stream( java.util.stream.Stream). Nó chứa rất nhiều phương thức hữu ích mà bạn có thể sử dụng biểu thức lambda. Chúng tôi chuyển biểu thức lambda x -> x*xcho phương thức map()để áp dụng biểu thức đó cho tất cả các phần tử trong luồng. Sau đó chúng ta sử dụng forEachđể in tất cả các phần tử của danh sách.
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
    int x = n * n;
    System.out.println(x);
}
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x) -> x*x).forEach(System.out::println);
Cho một danh sách, bạn cần in tổng bình phương của tất cả các phần tử trong danh sách. Biểu thức Lambda cho phép bạn đạt được điều này bằng cách chỉ viết một dòng mã. Ví dụ này sử dụng phương pháp tích chập (rút gọn) reduce(). Chúng ta sử dụng một phương thức map()để bình phương từng phần tử, sau đó sử dụng một phương thức reduce()để thu gọn tất cả các phần tử thành một số duy nhất.
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = 0;
for(Integer n : list) {
    int x = n * n;
    sum = sum + x;
}
System.out.println(sum);
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();
System.out.println(sum);

Sự khác biệt giữa biểu thức Lambda và các lớp ẩn danh

Sự khác biệt chính là việc sử dụng từ khóa this. Đối với các lớp ẩn danh, từ khóa '' thisbiểu thị một đối tượng của lớp ẩn danh, trong khi trong biểu thức lambda, ' this' biểu thị một đối tượng của lớp trong đó biểu thức lambda được sử dụng. Một điểm khác biệt nữa là cách chúng được biên soạn. Java biên dịch các biểu thức lambda và chuyển đổi chúng thành privatecác phương thức lớp. Điều này sử dụng lệnh gọi động , được giới thiệu trong Java 7 để liên kết phương thức động. Tal Weiss đã mô tả trong blog của mình cách Java biên dịch các biểu thức lambda thành mã byte

Phần kết luận

Mark Reinhold (Kiến trúc sư trưởng của Oracle) gọi biểu thức Lambda là thay đổi quan trọng nhất trong mô hình lập trình từng xảy ra - thậm chí còn quan trọng hơn cả các biểu thức generic. Chắc hẳn anh ấy đúng, bởi vì... họ cung cấp cho các lập trình viên Java khả năng ngôn ngữ lập trình chức năng mà mọi người đã chờ đợi. Cùng với những cải tiến như phương pháp mở rộng ảo, biểu thức Lambda cho phép bạn viết mã chất lượng rất cao. Tôi hy vọng bài viết này đã giúp bạn có cái nhìn sâu hơn về Java 8. Chúc may mắn :)
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION