Lambda expression không khó

Java 8 bổ sung thêm khá nhiều tính năng, trong đó có cú pháp lambda expression. Nói đơn giản, đây là một cách implement một functional interface ngắn gọn hơn so với cách dùng class hoặc anonymous class. Bạn có thể xem lại phần tổng quan ở bài 2 nhé. Về cú pháp lambda thì

Java 8 bổ sung thêm khá nhiều tính năng, trong đó có cú pháp lambda expression. Nói đơn giản, đây là một cách implement một functional interface ngắn gọn hơn so với cách dùng class hoặc anonymous class. Bạn có thể xem lại phần tổng quan ở bài 2 nhé.

Về cú pháp lambda thì trong những bài trước mình có ví dụ sơ sơ rồi. Và bài hôm nay mình sẽ đi sâu hơn về nó nhé.

1. Cú pháp chung

Cú pháp lambda khá giống cách viết arrow function trong JavaScript. Cũng gồm có 3 phần như sau:

  • Danh sách tham số
  • Arrow token (dấu ->)
  • Phần body chứa code

Ví dụ với đoạn code này nhé.

List.of("John","Mike").stream().forEach((String name)->{System.out.println(name);});

Thì phần sau chính là một lambda expression (do là expression nên gán cho biến được).

Consumer<String> printName =(String name)->{System.out.println(name);}

Cũng cần chú ý là dạng của lambda phải phù hợp với method của functional interface. Ví dụ operation filter() cần một Predicate<T> mà bạn đưa lambda dạng Function<T, R> là dở rồi 😁

2. Rút gọn lambda

Trình biên dịch của Java càng ngày càng thông minh, nên nó có thể suy luận được một số kiểu dữ liệu (type inference). Do đó, chúng ta không cần viết dạng đầy đủ cho lambda như trên mà có thể rút gọn xuống như sau.

Đầu tiên có thể bỏ kiểu dữ liệu của lambda param đi như sau.

Consumer<String> printName =(name)->{System.out.println(name);}

Nhưng làm sao Java biết được name có kiểu gì? Ở đoạn code đầu tiên , chúng ta có danh sách là List<String>, nên stream lấy ra là Stream<String>. Từ đó suy ra được TString, như vậy name cũng là String.

Thứ hai, nếu lambda chỉ có 1 param như trên thì bỏ dấu ngoặc đơn () như sau.

// Có đúng 1 param thì bỏ đượcConsumer<String> printName = name ->{System.out.println(name);}// Nhưng có 0 hoặc từ 2 param trở lên thì phải cóSupplier<Integer> five =()->{return5;}BiPredicate<Integer,Integer> isBigger =(a, b)->{return a > b;}

Cuối cùng là xem xét bỏ luôn ngoặc nhọn {} đi luôn. Dùng khi body chỉ có một lệnh, nhiều lệnh thì phải có ngoặc nhọn bao lại.

// Nếu chỉ có một lệnh nào đó (không return)Consumer<Integer> print = num ->System.out.println(num);// Nếu lệnh duy nhất là return thì không cần ghi returnFunction<String,Integer> getLength = str -> str.length();// ĐúngFunction<String,Integer> getLength = str ->return str.length();// Sai

Từ Java 10, có thể dùng var thay vì để Java tự suy luận kiểu ngầm định như trên.

Consumer<Integer> print =(var num)->System.out.println(num);

Ơ nhưng tại sao dùng thêm var làm chi, để trống nó cũng suy luận ra được vậy? À, lý do bởi vì nếu lambda param có thêm các modifier (như final) hoặc annotation thì để trống kiểu không được. Lúc đó thì dùng var thế vào, vừa vẫn được suy luận kiểu, vừa không bị lỗi.

// Lỗi do thiếu kiểu dữ liệuConsumer<Integer> print =(final num)->System.out.println(num);// Đúng, do var được xem là kiểu dữ liệu tự suy luậnConsumer<Integer> print =(finalvar num)->System.out.println(num);

3. Capturing lambda?

Bên trong body của lambda bạn có thể khai báo các biến local bình thường (như trong method). Ngoài ra, lambda còn có thể truy cập tới các biến bên ngoài.

// Biến bên ngoài đâyint increment =1;Function<Integer,Integer> increase = num ->{// Truy cập được biến bên ngoàireturn num + increment;};

Do cách hoạt động của lambda, người ta phân lambda thành 2 loại:

  • Non-capturing: lambda không truy cập tới biến nào bên ngoài nó
  • Capturing: lambda có truy cập tới biến bên ngoài

Code ví dụ trên là capturing lambda, và biến được capture chính là increment.

Và Java có áp dụng quy tắc lên trên biến bên ngoài được lambda capture. Đó là biến capture cần phải là final hoặc effectively final (chả biết dịch sao).

Nói chung nghĩa là lambda không được phép sửa biến mà nó capture.

Trước đây với anonymous class thì bắt buộc có final, nhưng lambda đỡ hơn chỗ cho phép cả effectively final (không cần final, nhưng không được sửa biến đó).

int increment =1;Function<Integer,Integer> increase = num ->{
    increment =2;// Lỗi do sửa biến, làm mất tính effectively finalreturn num + increment;};

Okay bài đến đây thôi. Tóm lại các bạn đã nắm được cách sử dụng lambda cơ bản rồi. Nếu các bạn có hứng thú tìm hiểu sâu hơn về lambda, cách nó hoạt động, lambda có phải là syntax sugar của anonymous class không,… hãy đón chờ bài viết sau nhé.

Như mọi khi, bài viết cũng được đăng trên blog cá nhân của mình https://tonghoangvu.hashnode.dev/lambda-expression-khong-kho.

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ