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é.
1 2 3 4 5 |
<span class="token class-name">List</span><span class="token punctuation">.</span><span class="token function">of</span><span class="token punctuation">(</span><span class="token string">"John"</span><span class="token punctuation">,</span> <span class="token string">"Mike"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token class-name">String</span> name<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
Thì phần sau chính là một lambda expression (do là expression nên gán cho biến được).
1 2 3 4 |
<span class="token class-name">Consumer</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">String</span><span class="token punctuation">></span></span> printName <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">String</span> name<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> |
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.
1 2 3 4 |
<span class="token class-name">Consumer</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">String</span><span class="token punctuation">></span></span> printName <span class="token operator">=</span> <span class="token punctuation">(</span>name<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> |
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 T
là String
, 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<span class="token comment">// Có đúng 1 param thì bỏ được</span> <span class="token class-name">Consumer</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">String</span><span class="token punctuation">></span></span> printName <span class="token operator">=</span> name <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// Nhưng có 0 hoặc từ 2 param trở lên thì phải có</span> <span class="token class-name">Supplier</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">Integer</span><span class="token punctuation">></span></span> five <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token number">5</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token class-name">BiPredicate</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">Integer</span><span class="token punctuation">,</span> <span class="token class-name">Integer</span><span class="token punctuation">></span></span> isBigger <span class="token operator">=</span> <span class="token punctuation">(</span>a<span class="token punctuation">,</span> b<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> a <span class="token operator">></span> b<span class="token punctuation">;</span> <span class="token punctuation">}</span> |
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.
1 2 3 4 5 6 7 |
<span class="token comment">// Nếu chỉ có một lệnh nào đó (không return)</span> <span class="token class-name">Consumer</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">Integer</span><span class="token punctuation">></span></span> print <span class="token operator">=</span> num <span class="token operator">-></span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>num<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Nếu lệnh duy nhất là return thì không cần ghi return</span> <span class="token class-name">Function</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Integer</span><span class="token punctuation">></span></span> getLength <span class="token operator">=</span> str <span class="token operator">-></span> str<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Đúng</span> <span class="token class-name">Function</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Integer</span><span class="token punctuation">></span></span> getLength <span class="token operator">=</span> str <span class="token operator">-></span> <span class="token keyword">return</span> str<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Sai</span> |
Từ Java 10, có thể dùng var
thay vì để Java tự suy luận kiểu ngầm định như trên.
1 2 |
<span class="token class-name">Consumer</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">Integer</span><span class="token punctuation">></span></span> print <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">var</span> num<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>num<span class="token punctuation">)</span><span class="token punctuation">;</span> |
Ơ 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.
1 2 3 4 5 6 |
<span class="token comment">// Lỗi do thiếu kiểu dữ liệu</span> <span class="token class-name">Consumer</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">Integer</span><span class="token punctuation">></span></span> print <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">final</span> num<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>num<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Đúng, do var được xem là kiểu dữ liệu tự suy luận</span> <span class="token class-name">Consumer</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">Integer</span><span class="token punctuation">></span></span> print <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">final</span> <span class="token keyword">var</span> num<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>num<span class="token punctuation">)</span><span class="token punctuation">;</span> |
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.
1 2 3 4 5 6 7 8 |
<span class="token comment">// Biến bên ngoài đây</span> <span class="token keyword">int</span> increment <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token class-name">Function</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">Integer</span><span class="token punctuation">,</span> <span class="token class-name">Integer</span><span class="token punctuation">></span></span> increase <span class="token operator">=</span> num <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token comment">// Truy cập được biến bên ngoài</span> <span class="token keyword">return</span> num <span class="token operator">+</span> increment<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> |
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 đó).
1 2 3 4 5 6 7 |
<span class="token keyword">int</span> increment <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token class-name">Function</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">Integer</span><span class="token punctuation">,</span> <span class="token class-name">Integer</span><span class="token punctuation">></span></span> increase <span class="token operator">=</span> num <span class="token operator">-></span> <span class="token punctuation">{</span> increment <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token comment">// Lỗi do sửa biến, làm mất tính effectively final</span> <span class="token keyword">return</span> num <span class="token operator">+</span> increment<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> |
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.